目录
CAS是什么
CAS : compare and swap
CAS要做的事情就是 : 拿着寄存器的值和另外一个内存的值 进行比较 , 如果值相同了 , 就把另一个寄存器的值 , 和当前的这个内存的值进行交换.
标准库中的CAS
标准库中的 AtomicInteger 能够保证++ -- 的时候线程安全(保证线程安全的并不是加锁. 而是CAS).
两个线程对变量num分别自增5000次 :
import java.util.concurrent.atomic.AtomicInteger;
public class Demo2 {
public static void main(String[] args) {
AtomicInteger num = new AtomicInteger(0);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
num.getAndIncrement();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
num.getAndIncrement();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num.get());
}
}
利用CAS , 就可以实现上述功能, 既有线程安全 , 而且还比suychronized 更高效, synchronized 会涉及到锁的竞争 , 两个线程都要互相等待 , CAS 不涉及到线程阻塞等待.
另外AtomicInteger 中还有 :
public static void main(String[] args) {
AtomicInteger num = new AtomicInteger(0);
num.getAndIncrement(); // num++;
num.getAndDecrement(); // num--;
num.decrementAndGet(); // --num;
num.incrementAndGet(); // ++num;
}
AtomicInteger 中的++操作实现过程 :
CAS本身对应一条cpu指令 (不可拆分的最小单位) , 此时上述的比较和交换动作是没法再拆的!! 具体实现由硬件支持
二 . 自旋锁(CAS)
反复检查当前的锁状态 , 看是否解开了.
我们认为自旋锁是一个轻量级锁 , 也可以视为是一个乐观锁~~
当前这把锁虽然没能立即拿到 , 预期很快就能拿到 , (我们认为锁冲突并不激烈)
虽然一直处于忙等 , 当时短暂的自旋几次, 浪费点cpu, 问题都不大, 自旋锁的好处就是只要这边锁一释放 , 就能立即的拿到锁~
三 . CAS 中的ABA问题
CAS 中的关键 , 是 先比较 , 再交换~~
比较其实是在比较 当前值 和 旧值是不是相同.
把这个两个值相同 , 就视为是中间没有发生过改变.
但是这里的结论才在漏洞, 当前值 和 旧值相同可能是中间确实没改变过 , 也有可能变了 , 但是又变回来了.
在特殊情况下 : 万一对比的时候是相同的, 但是不是没变过 , 而是从 A -> B -> A.
好比是 : 内存中的原本是 A 将过其他的线程改变为B , 又被改为A .
这样的漏洞 , 在大多数情况下 , 其实没影响 , 但是 , 极端情况下也会引起Bug.
解决方案有两种 :
1. 约定数据的值只能单方向变化 , (只能增加或者减小)
2. 如果是既要增加也要减小 , 可以引入另一个版本号 变量 , 约定版本号只能增加(每次修改,都会让版本号更新),
因此只要约定版本号 , 只能递增, 就能保证此时不会出现ABA反复横跳的问题 , 以版本号为基准, 而不是以变量数值为基准了!