上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人
2.7 Java中的原子性操作
所谓原子性操作,是指执行一系列操作时,这些操作要么全部执行,要么全部不执行,不存在只执行其中一部分的情况。在设计计数器时一般都先读取当前值,然后+1,再更新。这个过程是读—改—写的过程,如果不能保证这个过程是原子性的,那么就会出现线程安全问题。如下代码是线程不安全的,因为不能保证++value是原子性操作。
public class ThreadNotSafeCount { private Long value; public Long getCount() { return value; } public void inc() { ++value; } }
使用Javap -c命令查看汇编代码,如下所示。
public void inc(); Code:
0: aload_0 1: dup 2: getfield #2 // Field value:J 5: lconst_1 6: ladd 7: putfield #2 // Field value:J 10: return
由此可见,简单的++value由2、5、6、7四步组成,其中第2步是获取当前value的值并放入栈顶,第5步把常量1放入栈顶,第6步把当前栈顶中两个值相加并把结果放入栈顶,第7步则把栈顶的结果赋给value变量。因此,Java中简单的一句++value被转换为汇编后就不具有原子性了。
那么如何才能保证多个操作的原子性呢?最简单的方法就是使用synchronized关键字进行同步,修改代码如下。
public class ThreadSafeCount { private Long value; public synchronized Long getCount() { return value; } public synchronized void inc() { ++value; } }
使用synchronized关键字的确可以实现线程安全性,即内存可见性和原子性,但是synchronized是独占锁,没有获取内部锁的线程会被阻塞掉,而这里的getCount方法只是读操作,多个线程同时调用不会存在线程安全问题。但是加了关键字synchronized后,同一时间就只能有一个线程可以调用,这显然大大降低了并发性。你也许会问,既然是只读操作,那为何不去掉getCount方法上的synchronized关键字呢?其实是不能去掉的,别忘了这里要靠synchronized来实现value的内存可见性。那么有没有更好的实现呢?答案是肯定的,下面将讲到的在内部使用非阻塞CAS算法实现的原子性操作类AtomicLong就是一个不错的选择。