持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第22天,点击查看活动详情
CAS乐观锁及其引发的ABA问题
对于锁,相信大家都不陌生;如果是Java方向的朋友应该对synchronized也是有自己的理解的,但是今天,咱们来学习另外的一个东西——CAS。
什么是CAS?
CAS,其实是compare and swap (set),直译过来就是比较和设置。
下面以 compareAndSwapInt 为例进行讲解(其实还有 compareAndSwapLong、 compareAndSwapObject......)
/**
* Atomically update Java variable to <tt>x</tt> if it is currently
* holding <tt>expected</tt>.
* @return <tt>true</tt> if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
- 第一个参数 Object o 是指目标对象
- 第二个参数 long offset 其实是一个内存指针,表示该成员变量在其对应对象属性的偏移量,即通过offset可以直接在内存中找到该变量,从而执行CAS操作。
- 第三个参数 int expected 是指预期值,是旧值(期待没改之前,该变量值为 expected)
- 第四个参数 int x 是指目标值,指要将改变量改为什么值
在执行CAS操作的时候,首先看看该变量的值是否等于预期值expected,如果符合这执行下一步操作,否则停止。
CAS乐观锁
首先有一点需要提前点出来的就是:CAS乐观锁,实际上是没有加锁的!
下面我们来看看它的一个具体应用(操作):
/**
* Atomically adds the given value to the current value of a field
* or array element within the given object <code>o</code>
* at the given <code>offset</code>.
*
* @param o object/array to update the field/element in
* @param offset field/element offset
* @param delta the value to add
* @return the previous value
* @since 1.8
*/
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
上面的代码是一个整型变量自增的流程,它是线程安全的;但是心细的朋友肯定已经发现了,上述代码从头到尾都没有出现过 synchronized 你怎么说它是线程安全的呢?
那是因为它内部使用的是CAS来实现自增的,并不是普通的 ++ 或者 + 1 !
compareAndSwapInt()中如果发现变量值和预期值不一致的时候,说明变量值已经被其他线程修改过了,此时是不会设置新值的。
但是从上述代码可以看到compareAndSwapInt()是一个while循环的操作 while (!compareAndSwapInt(o, offset, v, v + delta)),也就是说它会不断地循环,直到完成一次修改。
既然已经被其他线程修改了,为什么这里还循环CAS,不会出现问题吗?
其实是不会的,因为每次循环 compareAndSwapInt() 之前,都是先重新获取一下内存中该变量的最新的值,这样确保每次都是对最新的值进行操作就不会出现线程不安全的问题了(典型的应用场景:多线程同时对一个变量操作增加)。
什么是ABA问题?
对于某个变量x,线程1在执行一次CAS操作的时候,预期值是A的;但是在这之前,变量X的值被线程2先从A改成了B,可是又从B改成了A;这时候,线程1执行CAS操作的时候发现变量x的值是A 和 它的预期值A一样。
上述场景提到的问题就是经典的ABA问题!
其实在对基本数据类型的操作时发生ABA问题是没啥大影响的,一般情况下可以直接忽略,线程该干嘛干嘛。
但是!
但是如果CAS操作针对的不是基本数据类型,而是某个引用类型的某个对象就可能有大问题:
正如上图所示,虽然在线程A来的时候,引用的还是对象o,但是这个对象o已经不是之前的那个对象o了。
如何解决ABA问题?
其实想解决ABA问题方法也是比较简单的,只需要在数据中添加一个version字段即可(表示该数据的版本号);以后只要该数据一经修改,版本号就 + 1;最终在执行 compareAndSwapObject() 操作的时候,不仅要比较其预期值是否符合,还要比较版本号;要两者一致才开始更新!