这是一条现代 CPU 的硬件同步原语,即由 CPU 保证其原子性。
需要提供三个参数:内存位置、预期值、新值。如果内存位置的值与预期值相匹配,那么处理器会自动将该位置更新为新值,否则不做任何操作。
Java 通过 JNI ( Java Native Interface ) 调用 C/C++ 方法来执行 CPU 的 CAS 指令,完成该任务。
这是 Java 的 J.U.C. ( java.util.concurrent ) 包实现原子操作的基础。
我们来举个例子讲一讲。
单线程下可以这种操作:
if (a == b) a++;
但多线程下我们要么加锁,要么使用“自旋 CAS”(自旋 == 忙等待):
volatile int value;
for (;;) {
int curValue = get();
if (compareAndSet(curValue, newValue))
return curValue;
}
JNI 调用长这样:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
一些问题
ABA 问题
CAS 不能检测 ABA 问题,即某变量虽然被多次修改(A->B->A),却最终改回了期望值,这将导致 CAS 成功,实际上该变量被修改过你却不知道!在这种情况下,考虑对每个操作添加版本号计数来避免。
从 Java 1.5开始 atomic 包提供了 AtomicStampedReference 类来解决 ABA 问题。这个类的 compareAndSet 方法首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
关于ABA问题参考文档: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
只能保证一个共享变量的原子操作
有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。从 Java 1.5 开始提供了 AtomicReference 类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行 CAS 操作。
资源竞争严重时慎用
自旋 CAS 在资源竞争严重时,会导致频繁失败,浪费 CPU 时间,耗尽分配给它的时间片。Java 1.6 之后改进过的 synchronized
原语在此时能获得更好的性能。
网友评论