上一篇讲了在Java中同步访问共享的可变数据有两种方法,一个是使用sychronized
关键字来同步方法或同步块,这种方法能同步数据并且还能实现访问互斥。另外一种方法是使用volatile
修饰符,它仅仅实现了线程之间的交互通信。
假设我们现在需要实现一个简单的计数器,它是一个不重复且唯一的递增数值。我们使用volatile
来实现看下:
1 | public class Counter { |
输出结果:
1 | Thread-0: 2 |
可以看到上述结果有很多值都是重复的,这显然不是我们想要的效果。我们的方法是确保每个调用都返回不同的值。
问题在于,增量操作符++
不是原子的。它在num
域中执行两项操作:首先它读取值,然后写回一个新值,相当于原来的值再加上1。如果这时候第二个线程在第一个线程读取旧值和写回新值期间读取这个域,第二个线程就会和第一个线程一起看到同一个值,并返回相同的新值。这就是安全性失败。
要修正incrNum
方法的一种方法是在它的声明中添加synchronized
修饰符来实现互斥。这样可以确保多个调用不会交叉存取,每个调用都会看到之前所有调用的效果,然后num
的修饰符volatile
就可以删除了,因为synchronized
已经达到了同步的效果。像下面这样:
1 | private static int num = 0; |
其它地方不需要改动,这样就可以达到想要的效果了。为了让这个方法更可靠,最好用long
代替int
。
还有第二种方法也可以实现相同的效果:使用类AtomicLong
,它是java.util.concurrent.atomic
的一部分,它比synchronized
修饰符执行得更好:
1 | public class Counter { |
输出结果:
1 | Thread-0: 1 |
那么AtomicLong
这个类的原理是什么呢?看这一篇理解Java中的AtomicLong类。