- 原文地址:Compare and Swap
- 作者: Jakob Jenkov
比较并交换,是一个并发算法中常用的技术。 基本上,比较并交换就是将变量的预期值与一个具体值惊醒比较,如果具体值等于预期值,则拿一个新的值与变量交换,这样变量就更新成新的值了。 比较并交换听起来有些复杂,但实际上并不复杂。
适用什么场景
在程序中并发算法会有一个非常常见的模式,称之为:“根据某个状态值来决定如何操作”。("检查-然后-动作") 如下面这个例子:
class MyLock {
private boolean locked = false;
public boolean lock() {
if(!locked) {
locked = true;
return true;
}
return false;
}
}
上面这段代码,在多线程中会有问题,这里的并发问题,我们暂时忽略。
lock()
方法,首先检查了locked
变量是否等于false
(检查),如果相等则设置locked
为true
(动作)
如果同一个
MyLock
实例,被多线程访问,那lock()
并不能正常工作。 如果线程A检查了locked
的值为false
,同一时间,线程B也检查locked
为false
。(或者在线程A更新locked
之前) 这样就会导致并发问题,线程A和B都可以看到locked
为false
,然后都会基于false做出各自动作。
要让这个代码能在多线程下工作正常,就需要保障“检查-然后-动作”是一个原子操作。 原子操作就是让“检查”和后续的“动作”,在执行时不会被分割成多个步骤,而是一起执行。 任何线程开始执行这个过程就不会被其他线程干扰。 也即是,原子操作同一时间只会被一个线程执行。
下面,通过
synchronized
来改造:
class MyLock {
private boolean locked = false;
public synchronized boolean lock() {
if(!locked) {
locked = true;
return true;
}
return false;
}
}
现在
lock()
方法是一个synchronized
方法,这样也就保障了同一个MyLock
实例的lock()
方法同一时间只会被一个线程执行。
原子化操作
现代CPU内部已经支持比较并交换的原子化操作指令。 从Java 5 开始,
java.util.concurrent.atomic
提供了利用CPU原子化指令的API。
下面就使用原子化操作来改造上例中的
lock()
方法。
public static class MyLock {
private AtomicBoolean locked = new AtomicBoolean(false);
public boolean lock() {
return locked.compareAndSet(false, true);
}
}
将原有的boolean类型的变量,改成了
AtomicBoolean
类型。AtomicBoolean
有一个compareAndSet()
方法,通过这个方法,就可以原子化的执行比较并交换的过程。
compareAndSet()
方法返回true表示已经交换,返回false则说明没有交换。
利用这套API的最大好处就是,底层可以充分利用CPU的原子指令来优化。这样比手工代码的性能会高出很多!!!