CAS(Compare And Swap)比较和替换常用于设计并发算法.通常的做法是将预期值和一个确切的变量相比较,如果相等,则将变量替换为新的值.CAS并没有想象中的复杂,只要你理解了其实特别的简单.
什么样的场景中可以用到CAS
在并发算法和程序中“先检查后执行”是一个很常见的模式.即先检查一个变量值然后基于这个变量值执行.如下所示:
class MyLock {
private boolean locked = false;
public boolean lock() {
if(!locked) {
locked = true;
return true;
}
return false;
}
}
这段代码在并发环境中会有线程安全问题,在这里我们先忽略这点.
我们可以看到,在lock()方法中,首先去检查变量locked是否为false(先检查),如果是的话则将它设置为true(后执行).
如果多个线程同时调用同个MyLock实例,上面的lock()方法将不能保证是线程安全的.如果线程A此时检查变量locked发现它为false,同时线程B也能够对locked进行检查.事实上只要线程A在没有设置locked变量为false之前,其他线程都能对locked变量进行检查.所以线程A和B都能检查和发现locked变量为false,并且都能在此之上执行.
为了确保在多线程应用中是线程安全的,“先检查后执行”操作必须是原子的.原子的意思是检查和执行必须在同一个原子代码块中执行,而不能分开.任何一个线程在执行原子代码块的过程中都不会受到其他线程的干扰直到结束为止.没有线程能在同一时间执行原子代码块.
下面将上文提及的实例更改为使用synchronized关键字实现的原子代码块.
class MyLock {
private boolean locked = false;
public synchronized boolean lock() {
if(!locked) {
locked = true;
return true;
}
return false;
}
}
现在lock()方式已经是同步的了,所以同一时间只能有一个线程能够在Mylock实例上执行.lock()方法同样已经是原子的了.
原子版的lock()方法已经是一个活生生的“比较和替换”的例子了.lock()方法先比较变量locked是否为预期值false,如果是的话,则将变量替换为true.
CAS即原子操作
现代CPU大部分已经内建支持原子的比较和替换操作.在Java5中,你可以使用java.util.concurrent.atomic包中的原子对象来使用CPU中的这些功能.
下面示例了上文lock()方法如何使用AtomicBoolean对象.
public static class MyLock {
private AtomicBoolean locked = new AtomicBoolean(false);
public boolean lock() {
return locked.compareAndSet(false, true);
}
}
我们可以注意到locked已经不是基础布尔类型,而是AtomicBoolean类型.这个对象有一个compareAndSet()方法用于比较AtomicBoolean实例值,若实例值等于一个预期值,则将它替换为新的值.在例子中,我们将比较locked值是否为false,如果是则将它替换为true.
compareAndSet()方法如果成功替换值则返回true,否则返回false.
使用Java5+中所提供原子对象的CAS功能,比起你自己实现的CAS带来的优势是能够使用CPU中的比较和替换特性.这比自己实现的CAS性能要优越得多.
该系列博文为笔者复习基础所著译文或理解后的产物,复习原文来自Jakob Jenkov所著Java Concurrency and Multithreading Tutorial