【Beautiful JUC Part.6】CAS 不可中断的典范
一、什么是CAS
运用场合:并发场景,实现不能被打断的交换操作
主要思路:
- 我认为V的值应该是A,如果是的话那我就把它改成B,如果不是A(说明被别人修改过了),那我就不修改了,避免多人同时修改导致出错。
- CAS有三个操作数:
- 内存值V、预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才将内存值修改为B,否则什么都不做。最后返回现在的V值
- CPU的特殊指令
- 一个指令可以比较和赋值
二、CAS的等价代码、应用场景
1、演示案例
public class TwoThreadsCompetition implements Runnable{
private volatile int value;
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
if (oldValue == expectedValue) {
value = newValue;
}
return oldValue;
}
@Override
public void run() {
compareAndSwap(0, 1);
}
public static void main(String[] args) throws InterruptedException {
TwoThreadsCompetition r = new TwoThreadsCompetition();
r.value = 0;
Thread t1 = new Thread(r, "线程1");
Thread t2 = new Thread(r, "线程2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(r.value);
}
}
模拟CAS操作
2、应用场景
- 乐观锁
- 并发容器
- concurrentHashMap
- 原子类
3、分析原子类的CAS
AtomicInteger类
AtomicInteger加载Unsafe工具,用来直接操作内存数据
用Unsafe来实现底层操作
用volatile修饰value字段,保证可见性
getAndAddInt方法分析
Unsafe类
总结
三、CAS的缺点
1、ABA问题
因为CAS只是做和原值相不相等的检查,并没有检查是否被修改。
假设有三个线程,原值是5,线程2把5改为7,线程3又把7改成5,等到第一个线程查看是否是5,发现真的是5,线程1以为没有任何线程对其进行修改,所以线程1就会把这个5改成所期待的值。但是实际上已经有人修改过了。
2、如何解决
可以添加版本号,比如说第一个版本的5,第二个版本的5
3、自旋时间过长
在原子类的getAndAddInt方法中可以看到,一直在cas部分自旋,如果一直等待锁,就会造成消耗资源的问题。