『阿里巴巴』Java并发编程之CAS第三篇-CAS的缺点( 二 )


所谓的ABA就是:在某个监控点的时候数据是A , 当过了时间N之后 , 在监控的时候还是A 。 但是在时间N的这段时间内 , 监控点的数据有可能不是A了 , 变成过B 。 这样就更容易理解了吧 。
ABA问题演示代码:
代码说明:
初始的时候 , 给了变量值为2020.也就是V=2020.如上图1
在经过线程A一顿猛如虎的操作之后 , 搞出来2020 , 2021 , 2020.ABA的效果处理 。 如上图2.
Sleep了1秒是为了让线程A完成ABA操作的 。
然后 , 线程2在拿着自己副本的变量值A=2020 , 和主内存V进行比较 。 发现一致 , 就更新了2019.
运行结果如下图:
从运行结果来看 , 线程2也更新成功了 。 但是 , 这样是不对的 。 因为我们已经知道线程A对共享变量操作过了 。 那么针对CAS的这些缺点 , 应该怎么解决呢?欢迎继续学习下一篇 。 凯哥将介绍三个怎么解决 。 以及会讲解原子引用、时间戳原子引用两个问题 。
CAS缺点解决办法一:循环长 , 开销大解决方案解决思路:ConcurrentHashMap(后面凯哥也会详细介绍的)类似的方法 。 当多个线程竞争时 , 将粒度变小 , 将一个变量拆分为多个变量 , 达到多个线程访问多个资源的效果 , 最后再调用sum把它合起来 。
二:一个共享变量的解决方案因为CAS只能一个共享变量一个共享变量的处理 。 如果想要处理类是代码块或者对象的 。 可以使用同步锁或者是多个变量放到一个对象里面 。 然后在CAS 。 因为在JUC包下 , 有支持对象的原子类 , 如:AtomicReference(原子引用类) 。
原子引用在Java中变量的类型分为八大基本类型或八大基本类型的对象类型或者是自定义的对象类型 。 在并发中 , atomicInteger就是基本类型就是int/Integer的原子类 。 那么自定义的对象怎么实现原子性呢?这就要用到原子引用对象- AtomicReference 。
原子引用demo:我们来模拟凯哥心中女神变化过程(注:女神同时只能存在一个 , 不能存在多个 , 要保持单一 , 原子的) 。
在X年之前是刘亦菲 , X+N年后是林依晨 , 现在是佟丽娅了 。 我们知道 , 这三个女神都是对象 。 都有年龄、用户名 , 是个对象 。
创建user对象
她们三个在凯哥心中活动如下:
那么请问在21和23行输入的结果是什么?
编辑
我们发现在23行依然输出的是林依晨 。 而不是佟丽娅 。 为什么呢?分析思路见:《Java并发编程之CAS一理解》篇文章的三:cas代码演示部分 。
我们修改之后再来看:
运行结果:
发现心中女神已经更新为佟丽娅了
三:ABA问题解决ABA问题产生的根本原因是因为:只是线程自己工作空间的变量预期值(副本)和主内存中的值进行了比较 。 当值相等的时候 , 就默认没有被其他线程更新过 。 那么怎么解决这个问题呢?
是不是可以添加一个东西 , 用来辅助呢?添加一个标记 , 或者一个版本号 , 根据版本号+数值来进行判断呢?当然可以了 , JDK中也是这么实现的 。 JDK使用的是时间戳(stamp) , 而不是我们说的版本号(version) 。 我们来看看时间戳原子引用(AtomicStampedReference<V>).
我们来看看这个类 。
时间戳原子引用demo先看构造器:
参数说明:
initialRef:初始值
initialStamp:初始值的时间戳
再来看看CompareAndSet方法:
参数说明:
expectedReference:预期值
newReference:更新值
【『阿里巴巴』Java并发编程之CAS第三篇-CAS的缺点】expectedStamp:预期时间戳值
newStamp:更改后时间戳值
我们发现这个AtomicStampedReference类和AtomicReference的方法中的区别就是时间戳原子引用类中的方法都添加了预期的时间戳值和修改后的时间戳的值这两个参数 。