通常情况下,在Java里面,++i或者–i不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作是“原子性”的。
Java 5新增了AtomicInteger类,该类包含方法getAndIncrement()以及getAndDecrement(),这两个方法实现了原子加以及原子减操作,但是比较不同的是这两个操作没有使用任何加锁机制,属于无锁操作。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。
拿AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。
private volatile int value;
首先毫无疑问,在没有锁的机制下需要借助volatile原语,保证线程间的数据是可见的(共享的),这样获取变量值的时候才能直接读取。
public final int get() {
return value;
}
然后来看看++i是怎么做到的(jdk1.7)。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。而compareAndSet利用JNI来完成CPU指令的操作。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
综上,我们可以总结出AtomicInteger实现CAS操作的大致思想。首先volatile保证每次get()方法取到的值都是公共栈内的最新值,当然,这并不保证此值接下来不会被其他线程修改。所以要在incrementAndGet()方法中进行检查,检查的关键是compareAndSet()方法,它首先通过this参数和valueOffset参数获得现在在公共栈中的AtomicInteger值,如果和expenct相同,那么说明get()方法取回的值并没有被修改,那么将AtomicInteger的value属性更新为update,并返回true;否则什么也不干并返回false。
valueOffset是一个long参数,代表AtomicInteger参数的value字段相对于此类的偏移量,通过这个参数配合AtomicInteger对象本身,可以找到此时的真实的value字段。
AtomicInteger实现CAS操作的基本原理可以总结为两句话:
1. 尽管AtomicInteger存储的字段可以被其他线程修改,但是存储字段的位置是不变的,这使得我们可以在一个线程内随时得到此类的真实value字段,这是进行CAS检查的基础。
2. 在线程内得到真实值后与原先值进行比较,也就是current字段,如果相同,则说明无其他线程修改这个值,可以进行替换,否则不进行替换。
在此基础上,我们可以试着描述如果在current+1过程中AtomicInteger的value字段被修改了会怎么样?
在jdk1.8中,incrementAndGet直接使用了Unsafe的getAndAddInt方法,而在jdk1.7的Unsafe中,没有此方法。
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}