第十五章 原子变量与非阻塞同步机制
一 比较并转换(CAS)
CAS包含了三个操作数:需要读写的内存位置V,进行比较的值A,和拟写入的新值B。当且仅当V的值等于A时,CAS才会通过源自方式用B来更新V的值,否则不会执行任何操作。
乐观锁就用到了此机制。
CAS的典型使用模式:
首先从V中读取值A,根据A计算值B,然后通过CAS以原子操作将V的值A变为B。
一个模拟的CAS:
/**
* 模拟CAS操作
* @author cream
*
*/
public class SimulatedCAS{
private int value;
public synchronized int get(){ return value;}
public synchronized int compareAndSwap(int expectedValue,int newValue){
int oldValue = value;
if(oldValue==expectedValue)
value = newValue;
return oldValue;
}
public synchronized boolean compareAndSet(int expectedValue,int newValue){
return (expectedValue == compareAndSwap(expectedValue, newValue));
}
}
使用CAS实现的线程安全的计数器:
class CASCounter{
private SimulatedCAS value;
public int getValue(){
return value.get();
}
public int increment(){
int v;
do{
v=value.get();
}
while(v != value.compareAndSwap(v, v+1));
return v+1;
}
}
递增操作采用了标准形式:读取旧的值,根据它计算出新值(+1),并使用CAS来设置这个新值。如果CAS失败,那么该操作将立即重试。
CAS缺点:CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作
ABA问题:因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
二 原子变量
AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。
private volatile int value;
首先在没有锁的机制下可能需要借助volatile原语,保证线程间的数据是可见的(共享的)。这样才获取变量的值的时候才能直接读取。
public final int get() {
return value;
}
然后来看看++i是怎么做到的。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。