在java中,如果要进行原子操作,我们可以通过加锁或CAS的方式来实现。其中,CAS虽然高效的解决了原子操作,但需要注意其存在的三个问题:ABA问题、自旋时间长开销大、只能保证一个共享变量的原子操作。
一、ABA问题
CAS 在修改变量值时,会先检查该变量的值是否和预期值一致,若一致则修改,引发的ABA问题的情况是:如一个变量初始值为A,被另外一个线程修改成B,再由B修改为A,此时使用CAS进行操作就检查不出变量的变化轨迹,并对该变量修改,这就是ABA问题,其前提条件是“节点可以被循环使用”。
ABA问题的解决办法是在变量前加上版本号,每次变量的修改都将版本号加1,此时 A → B → A 则变成 1A → 2B → 3A。从而避免ABA问题。
JDK 1.5 之后在并发包下也提供了对应的带邮戳的原子类 AtomicStampedReference以解决ABA问题。
/**
* 原子设置 reference 和 stamp 为给定的更新值,
* 当且当前的引用== 期望的引用,stamp和期望的stamp相等
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
public boolean compareAndSet(
V expectedReference, // 期望的引用
V newReference, // 新的引用
int expectedStamp,// 期望的stamp
int newStamp // 新的stamp
) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
二、自旋时间长开销大
如果CAS自旋长时间仍不成功,对CPU的开销是非常大的。
三、只能保证一个共享变量的原子操作
CAS可以保证单个共享变量的原子操作,对于多个共享变量,CAS无法保证原子性,当然此时可以使用锁来解决。另外一个方法是:把多个共享变量封装进一个对象,然后通过AtomicReference类保证引用对象之间的原子性。
参考:
1.《Java并发编程的艺术》-Java并发机制的底层实现原理(第2章)
2.《JAVA并发编程实战》,Doug lea