Java并发编程之美
上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就是一个不错的选择。