本文基本节选自
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; } } }