在Java中可以通过锁或循环CAS的方式来实现原子操作。但是CAS存在三大问题:ABA问题、循环时间长开销大,以及只能保证一个共享变量的原子操作。
ABA问题
CAS在操作值的时候,会先检查旧值是否发生变化,如果没有则更新。但是如果一个值原来是A,被改成B,后来又被改成A,那么使用CAS进行检查时会发现它的值没有发生变化,但实际上是改变了的。
解决思路:使用版本号
在变量前面加上版本号,每次变量更新的时候把版本号加1,那么A->B->A就会编程1A->2B->3A。
从Java1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
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就无法保证操作的原子性。
解决方法:使用锁。
当然还有一种取巧的方式,就是把多个共享变量合并成一个共享变量来操作。