Java多线程同步

同步

在实际中,我们可能遇到这样一个问题。假设线程 thread1 和 thread2 都对一个变量 i 进行操作,线程 thread1 每次让 i 加一,线程 thread2 每次让 i 减一。假设:

  • t1 时刻,thread1 读取 i 的数据为100;
  • t2 时刻(thread1 还没来得及加一),thread2 也读到了修改之前的数据 i,其值为100;
  • t3 时刻,thread1 执行 i 加一,将101写回到 i;
  • t4 时刻,由于之前thread2读到的数据为100,因此减一之后将99写回到 i。这个99被称为脏数据

这就是典型的同步问题,那么怎么解决呢?在thread1 读取 i 的时候,应该让这个线程对数据 i 上锁,使得其他线程无法读取变量 i。在thread1 运行结束之后,这个同步锁释放掉,其他进程才可以对 i 进行访问。

所以,Java中通过synchronized关键字,实现了同步的概念。

synchronized关键字

看一段 synchronized 关键字的用法:

Object obj =new Object();
	synchronized (obj){
		//这里的内容必须要得到这个obj对象后才能运行
		do_something();
}

这里的 obj 就相当于前面说的互斥锁,哪个进程得到这个对象,就可以独占这个锁,然后尽情的执行synchronized里面的代码;而其他进程需要等待这个obj被释放掉,才可以去抢这个锁。

除了将Object类的对象作为锁之外,所有继承Object的类的对象都可以当作同步锁。

synchronized方法

在一个类中,我们可以把所有方法都定义为synchronized方法,这样的类称为线程安全类。同一时间,只有一个进程可以进入这个类的一个实例进行数据的修改,以保证这个实例中不出现脏读。

看下面一段代码:

	public static String now(){
        return new SimpleDateFormat("HH:mm:ss").format(new Date());
    }
	
	public static class Person {
		String name;
		int hp;
		
		public synchronized void addHp(Thread t) {
			System.out.println(now() +  t.getName() + "占有对象obj");
			this.hp++;
			System.out.println("现在的hp为: " + this.hp);
			System.out.println(now() + t.getName() + "释放对象obj");
		}
		
		public synchronized void reduceHp(Thread t) {
			System.out.println(now() +  t.getName() + "占有对象obj");
			this.hp--;
			System.out.println("现在的hp为: " + this.hp);
			System.out.println(now() + t.getName() + "释放对象obj");
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//final Object obj = new Object();
		Person tom = new Person();
		tom.name = "tom";
		tom.hp = 100;
		Thread t1 = new Thread() {
			public void run() {
				System.out.println(now() + this.getName() + "线程开始运行");
				System.out.println(now() + this.getName() + "线程试图占用obj");				
				tom.addHp(this);
			}
		};
		t1.setName("t1");
		t1.start();
		
		Thread t2 = new Thread() {
			public void run() {
				System.out.println(now() + this.getName() + "线程开始运行");
				System.out.println(now() + this.getName() + "线程试图占用obj");
				tom.reduceHp(this);
			}
		};
		t2.setName("t2");
		t2.start();
	}

在Person类中,我们将所有的方法都定义为 synchronized 方法,所以这是一个线程安全类。对于每一个Person类的实例对象,虽然没有显式的写出来,但是这个同步锁相当于它自身,即this。

这样就好理解了,对于一个实例化的对象,如果有多个线程企图修改他的数据(调用他的方法),那么只能有一个线程在某个时刻可以得到这个对象(this,锁),并执行其代码。等到这个线程结束或抛出异常,其他线程才能去抢占这个锁(this)。

发布了34 篇原创文章 · 获赞 80 · 访问量 2022

猜你喜欢

转载自blog.csdn.net/jackzhang11/article/details/102673290