Java 并发(一) --- CAS

CAS 原理

      先来看看下面的代码是否可以输出预期的值.开启了两个线程,是否会输出200 呢

twoThreadConcurrent

      结果由于并发的原因,结果会小于或等于200 , 原因出现在

count++;

      由于这一行代码存在三个操作: 取值,+1,赋值. 但是由于这三个操作不是原子性的,有可能执行的时候其他线程读取到了,于是就产生了错误的值.

      那么我们在只要在这一行代码加上锁就可以避免并发问题.

synchronized(this){
    count++;
}

      但是使用synchronized加锁并不是最佳的选择,因为加锁会伴随性能的问题.

    Synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式内核模式的转换,代价比较高。

    尽管Java1.6为Synchronized做了优化,增加了从偏向锁轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。

       于是我们通过下面的原子类来解决类,原子类顾名思义就是里面的操作是原子性的.原子类使用的技术就是 CAS .

auto

       CAS (Compare and Swap) --- 对比并且交换. 什么意思呢?

        下面原理解释摘自参考文章:

1.在内存地址V当中,存储着值为10的变量。

2.此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。

3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。

4.线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。

5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋

6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。

7.线程1进行SWAP,把地址V的值替换为B,也就是12。

     从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。

CAS 存在的ABA 问题

      通过上面我们知道CAS 向对内存中的数值进行获取比较在进行赋值,只要数值相同就会赋值,但是如果某个数值被更改后又被更改回来,那么此时一样满足交换的条件,那么这样会不会产生问题呢?见

小灰_进阶_CAS

      同样来自上面文章的总结:

1. Java语言CAS底层如何实现?

     利用unsafe提供了原子性操作方法。

2. 什么是ABA问题?怎么解决?

     当一个值从A更新成B,又更新会A,普通CAS机制会误判通过检测。利用版本号比较可以有效解决ABA问题。

 

参考文章:

小灰_CAS

小灰_进阶_CAS

原文地址:https://www.cnblogs.com/Benjious/p/9992048.html