本文基本节选自
Java theory and practice: Going atomic
CAS字面上是什么意思?
CAS = Compare-and-Swap. 它的语义是:
public class SimulatedCAS {
private int value;
//如果某个值当前值是期望值,则将某个值设置为新值;否则不做设置.
//然后,返回当前值
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int currentValue = value;
if (value == expectedValue)
value = newValue;
return currentValue;
}
}
CAS在并发控制上起什么作用?
以“自增操作”为例,使用CAS可以避免常见的“丢失修改”问题
private SimulatedCAS value;
public int increment() {
int oldValue = value.getValue(); //CAS操作之前先拿一下当前值
while (value.compareAndSwap(oldValue , oldValue + 1) != oldValue ){ //如果CAS的返回值不等于CAS操作之前的值,那说明在CAS操作之前value已经被其他线程修改,那么本线程的修改就会造成“丢失修改”,所以这里要放弃修改;而应该重取一次当前值,并再来一次CAS,直到CAS操作成功
oldValue = value.getValue();
}
return oldValue + 1; //如果CAS操作成功,说明value在取当前值之后没被其他线程修改,所以这里可以返回 原值 + 1
}
另外你可能注意到,要保证compareAndSwap()操作的语义正确性,这个方法本身必须是原子操作。
要避免“丢失修改”问题,用synchronized关键字也能保证,为什么要用CAS? 因为CAS避免了锁,性能会比较好。
那上面的compareAndSwap()是原子方法,不也得用synchronized关键字、用锁了吗? 事实上的Java CAS操作是不需要用syncrhonzied关键字的,因为它根本不是通过java代码实现,而是依赖CPU或操作系统本地代码实现的,一般来说CPU直接支持CAS原语,从硬件上保障了CAS操作的原子性。
JRE中现成的CAS操作
可以观察一下如何用AtomicInteger这个带CAS功能的类来实现伪随机数。可以体会一下下面的代码,就不细说了。
public class PseudoRandomUsingSynch implements PseudoRandom {
private int seed;
public PseudoRandomUsingSynch(int s) { seed = s; }
public synchronized int nextInt(int n) {
int s = seed;
seed = Util.calculateNext(seed);
return s % n;
}
}
public class PseudoRandomUsingAtomic implements PseudoRandom {
private final AtomicInteger seed;
public PseudoRandomUsingAtomic(int s) {
seed = new AtomicInteger(s);
}
public int nextInt(int n) {
for (;;) {
int s = seed.get();
int nexts = Util.calculateNext(s);
if (seed.compareAndSet(s, nexts)) //compareAndSet是compareAndSwap的变体,它的返回值是true/false,代表本操作是否成功地修改了某变量
return s % n;
}
}
}