Atomiclnteger中的incrementAndGet方法就是乐观锁的一个实现,使用自旋(循环检测更新) 的方式来更新内存中的值并通过底层CPU执行来保证是更新操作是原子操作。方法如下:
public final int getAndAddlnt(Object varl, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(varl, var2);
} while (!this.compareAndSwapInt(varl, var2, var5, var5 + var4));
//可以看做 compareAndSwapInt (obj, offset, expect, update)
return var5;
}
首先这个方法通过getIntVolatile方法,使用对象的引用与值的偏移量得到当前值,然后调 用compareAndSwapInt检测如果obj内的value和expect相等,就证明没有其他线程改变过 这个变量,那么就更新它为update,如果这一步的CAS没有成功,那就采用自旋的方式继续 进行CAS操作。
在赋值的时候保证原子操作的原理是通过CPU的cmpxchgl与lock指令的支持来实现 AtomicInteger的CAS操作一定程度上的原子性
这个方法是先得到值,再更新值,所以必须保证更新的值是在原来的基础上更新的,所以采用 CAS进行更新,那么为什么不使用直接更新值然后返回值的方式来做呢?因为更新值的前提是 获取值,这是两部汇编级别的操作,仅仅更新值是无法获取到值的。
ABA问题
上面提到过lock指令,它能保证其他CPU无法参与进来,但是无法保证单个CPU的另一个线 程执行更新操作。所以如果一个值原来是A,变成了 B,又变成了 A,那么使用CAS进行检查 时会发现它的值没有发生变化,但是实际上却变化了。这就是CAS的ABA问题。
常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一, 那么A-B-A就会变成1A-2B-3A。
AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当 前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将 该引用和该标志的值设置为给定的更新值。
循环时间长开销大问题
上面我们说过如果CAS不成功,则会原地自旋,如果长时间自旋会给CPU带来非常大的执行开 销
Was this helpful?
0 / 0