CAS原理

CAS指令和具体源代码

源码说话,我们看一下getAndIncrement方法:

 1 //该方法功能是Interger类型加1
 2 public final int getAndIncrement() {
 3     //主要看这个getAndAddInt方法
 4     return unsafe.getAndAddInt(this, valueOffset, 1);
 5 }
 6 
 7 //var1 是this指针
 8 //var2 是地址偏移量
 9 //var4 是自增的数值,是自增1还是自增N
10 public final int getAndAddInt(Object var1, long var2, int var4) {
11     int var5;
12     do {
13       //获取内存值,这是内存值已经是旧的,假设我们称作期望值E
14        var5 = this.getIntVolatile(var1, var2);
15        //compareAndSwapInt方法是重点,
16        //var5是期望值,var5 + var4是要更新的值
17        //这个操作就是调用CAS的JNI,每个线程将自己内存里的内存值M
18        //与var5期望值E作比较,如果相同将内存值M更新为var5 + var4,否则做自旋操作
19     } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
20 
21     return var5;
22 }
解释一下getAndAddInt方法的流程:

假设有一下情景:

  1. A、B两个线程
  2. jvm主内存的值1,A、B工作内存的值为1(工作内存会拷贝一份主内存的值)
  3. 当前期望值为1,做加1操作
  4. 此时var5 = 1,var4 = 1;
    1. A线程将var5与工作内存值M比较,比较var5是否等于1
    2. 如果相同则将工作内存值修改为var5 + var4 即修改为2并同步到驻村,此时this + valueOffset指针里,示例变量value的值就是2,结束循环
    3. 如果不相同,则是B线程修改了主内存的值,说明B线程已经先于A线程做了加1操作,A线程没有更新成功需要继续循环,注意此时var5更新为新的内存值,假设当前的内存值是2,那么此时var5 = 2,var + var4 = 3,重复上述步骤直到成功(自旋),成功之后,内存地址中的值就改变为3

简单来说,就是将工作内存的值与主内存的值来进行比较,如果相等,说明没有被其他线程修改,则进行赋新值操作,如果不相等,说明主内存的数据被其他线程更改过,则此时需要更新工作内存的副本值,并且重新获取主内存的值,再次进行比较,直到二者相等为止;

CAS优缺点

  • 优点

非阻塞的轻量级的乐观锁,通过CPU指令实现,在资源竞争不激烈的情况下性能高,相比synchronized重量锁,synchronized会进行比较复杂的加锁、解锁和唤醒操作。

  • 缺点
  1. ABA问题: 线程C、D;线程D将A修改为B后又修改为A,此时C线程以为A没有改变过,java的原子类AtomicStampedReference,通过控制变量值的版本号来保证CAS的正确性。具体解决思路就是在变量前追加上版本号,每次变量更新的时候把版本号加一,那么A - B - A就会变成1A - 2B - 3A。
  1. 自旋时间过长,消耗CPU资源,如果资源竞争激烈,多线程自旋长时间消耗资源
原文地址:https://www.cnblogs.com/shuo1208/p/10761519.html