所谓原子性操作,是指执行一系列操作时,这些操作要么全部执行,要么全部不执行,不存在只执行其中一部分的情况。在设计计数器时一般都先读取当前值,然后 +1,再更新。这个过程是读—改—写的过程,如果不能保证这个过程是原子性的,那么就会出现线程安全问题。如下代码是线程不安全的,因为不能保证 ++value 是原子性操作。

    1. public class ThreadNotSafeCount {
    2. private Long value;
    3. public Long getCount() {
    4. return value;
    5. }
    6. public void inc() {
    7. ++value;
    8. }
    9. }

    使用 Javap -c 命令查看汇编代码,如下所示。

    1. public void inc();
    2. Code:
    3. 0: aload_0
    4. 1: dup
    5. 2: getfield #2 // Field value:J
    6. 5: lconst_1
    7. 6: ladd
    8. 7: putfield #2 // Field value:J
    9. 10: return

    由此可见,简单的 ++value 由 2、5、6、7 四步组成,其中第 2 步是获取当前 value 的值并放入栈顶,第 5 步把常量 1 放入栈顶,第 6 步把当前栈顶中两个值相加并把结果放入栈顶,第 7 步则把栈顶的结果赋给 value 变量。因此,Java 中简单的一句 ++value 被转换为汇编后就不具有原子性了。

    那么如何才能保证多个操作的原子性呢?最简单的方法就是使用 synchronized 关键字进行同步,修改代码如下。

    1. public class ThreadSafeCount {
    2. private Long value;
    3. public synchronized Long getCount() {
    4. return value;
    5. }
    6. public synchronized void inc() {
    7. ++value;
    8. }
    9. }

    使用 synchronized 关键字的确可以实现线程安全性,即内存可见性和原子性,但是 synchronized 是独占锁,没有获取内部锁的线程会被阻塞掉,而这里的 getCount 方法只是读操作,多个线程同时调用不会存在线程安全问题。但是加了关键字 synchronized 后,同一时间就只能有一个线程可以调用,这显然大大降低了并发性。你也许会问,既然是只读操作,那为何不去掉 getCount 方法上的 synchronized 关键字呢?其实是不能去掉的,别忘了这里要靠 synchronized 来实现 value 的内存可见性。那么有没有更好的实现呢?答案是肯定的,下面将讲到的在内部使用非阻塞 CAS 算法实现的原子性操作类 AtomicLong 就是一个不错的选择。