Java synchronized:实现线程同步(附带实例)
Java 提供 synchronized 关键字来实现同步机制,同步的主要作用是避免线程安全问题。
所谓同步是指控制多个线程对共享资源的访问,比如一次只能由一个线程访问,不能多个线程同时访问。在 synchronized 的作用范围内同一时间只有一个线程能进入,其他线程必须等待里面的线程出来以后才能依次进入。
synchronized 可以用来描述对象的方法,表示该对象的方法具有同步性。下面看一个实例:
在主线程中分别启动 10 个线程,并循环 10000 次去调用 add() 和 add2() 方法,然后让主线程休眠 3 秒,这是为了让所有线程都执行完毕。最终某次运行的输出结果为 count = 75608,count2 = 100000。
这里我们不关注 count 的具体值是多少,需要关注的是 count 的值肯定小于 100000,也就是说 add() 方法缺乏同步性而导致了线程安全 问题。而 count2=100000 则是正确的结果,因为 add2() 方法具备同步性。
有线程安全问题的 count 值为什么会小于 100000 呢?因为 count++ 等同于 count=count+1,假设某个时刻 10 个线程同时获取 count 的值为 100,那么 10 个线程中 count 的值加一后赋值给 count 变量的结果都为 101,而正确的结果应该是 110。
synchronized 也可以作用在对象的方法内部,即用 synchronized 来描述方法内部的某块逻辑,表示该块逻辑具有同步性。
我们看下面的程序:
这里的同步块包括了 count++ 操作,所以该操作具有同步性,也就能够避免线程安全问题。实际使用中同步块并不要求包含整个方法的所有代码,而可以是方法内的任意代码块。
在主线程上分别用 10 个线程循环调用 10000 次 add() 方法和 add2() 方法,最终运行的结果是 count = 100000,count2 = 100000。也就是说两个方法都获得了同步的效果,而且 add() 方法和 add2() 方法都使用了当前对象作为锁,所以这两个方法其实是共用一个锁的。
所谓同步是指控制多个线程对共享资源的访问,比如一次只能由一个线程访问,不能多个线程同时访问。在 synchronized 的作用范围内同一时间只有一个线程能进入,其他线程必须等待里面的线程出来以后才能依次进入。
synchronized 可以用来描述对象的方法,表示该对象的方法具有同步性。下面看一个实例:
public class ThreadSynchronizedTest { int count = 0; int count2 = 0; public void add() { count++; } public synchronized void add2() { count2++; } public static void main(String[] args) throws InterruptedException { ThreadSynchronizedTest2 test = new ThreadSynchronizedTest2(); for (int i = i < i++) new Thread(() -> { for (int j = j < j++) test.add(); }).start(); for (int i = i < i++) new Thread(() -> { for (int j = j < j++) test.add2(); }).start(); Thread.sleep(3000); System.out.println("count = " + test.count); System.out.println("count2 = " + test.count2); } }程序中定义了 add() 和 add2() 两个方法,其中 add2() 方法声明为 synchronized,这两个方法都是让各自的变量完成自增操作。
在主线程中分别启动 10 个线程,并循环 10000 次去调用 add() 和 add2() 方法,然后让主线程休眠 3 秒,这是为了让所有线程都执行完毕。最终某次运行的输出结果为 count = 75608,count2 = 100000。
这里我们不关注 count 的具体值是多少,需要关注的是 count 的值肯定小于 100000,也就是说 add() 方法缺乏同步性而导致了线程安全 问题。而 count2=100000 则是正确的结果,因为 add2() 方法具备同步性。
有线程安全问题的 count 值为什么会小于 100000 呢?因为 count++ 等同于 count=count+1,假设某个时刻 10 个线程同时获取 count 的值为 100,那么 10 个线程中 count 的值加一后赋值给 count 变量的结果都为 101,而正确的结果应该是 110。
synchronized 也可以作用在对象的方法内部,即用 synchronized 来描述方法内部的某块逻辑,表示该块逻辑具有同步性。
我们看下面的程序:
public class ThreadSynchronizedTest { int count = 0; int count2 = 0; public void add() { synchronized (this) { count++; } } public synchronized void add2() { count2++; } public static void main(String[] args) throws InterruptedException { ThreadSynchronizedTest6 test = new ThreadSynchronizedTest6(); for (int i = 0; i < 10; i++) new Thread(() -> { for (int j = 0; j < 10000; j++) test.add(); }).start(); for (int i = 0; i < 10; i++) new Thread(() -> { for (int j = 0; j < 10000; j++) test.add2(); }).start(); Thread.sleep(3000); System.out.println("count = " + test.count); System.out.println("count2 = " + test.count2); } }这里不像前面那样在方法上声明 synchronized,而是在 add() 方法内部通过
synchronized(this){xxx}
的形式来声明同步块。这里的同步块包括了 count++ 操作,所以该操作具有同步性,也就能够避免线程安全问题。实际使用中同步块并不要求包含整个方法的所有代码,而可以是方法内的任意代码块。
在主线程上分别用 10 个线程循环调用 10000 次 add() 方法和 add2() 方法,最终运行的结果是 count = 100000,count2 = 100000。也就是说两个方法都获得了同步的效果,而且 add() 方法和 add2() 方法都使用了当前对象作为锁,所以这两个方法其实是共用一个锁的。