CAS和ABA问题
一、引言
我们先来看一个多线程的运行场景:
时间点1 :线程1查询值是否为A
时间点2 :线程2查询值是否为A
时间点3 :线程2比较并更新值为B
时间点4 :线程2查询值是否为B
时间点5 :线程2比较并更新值为A
时间点6 :线程1比较并更新值为C
在这个线程执行场景中,2个线程交替执行。线程1在时间点6的时候依然能够正常的进行CAS操作,尽管在时间点2到时间点6期间已经发生一些意想不到的变化, 但是线程1对这些变化却一无所知,因为对线程1来说A的确还在。通常将这类现象称为ABA问题。
ABA发生了,但线程不知道。又或者链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
二、ABA问题隐患
获取上面的描述ABA问题带来的隐患没有直观的认识,那我们来看下维基百科上面的形象描述:
你拿着一个装满钱的手提箱在飞机场,此时过来了一个火辣性感的美女,然后她很暖昧地挑逗着你,并趁你不注意的时候,把用一个一模一样的手提箱和你那装满钱的箱子调了个包,然后就离开了,你看到你的手提箱还在那,于是就提着手提箱去赶飞机去了。
三、ABA问题解决
A a = ref.get(); // 根据a的状态做一些操作 // do something // CAS,这时候会出现ABA问题,a指向的对象可能已经变了 ref.compareAndSet(a, b)
ABA问题我们可以使用JDK的并发包中的AtomicStampedReference和 AtomicMarkableReference来解决。
// 用int做时间戳 AtomicStampedReference<QNode> tail = new AtomicStampedReference<CompositeLock.QNode>(null, 0); int[] currentStamp = new int[1]; // currentStamp中返回了时间戳信息 QNode tailNode = tail.get(currentStamp); tail.compareAndSet(tailNode, null, currentStamp[0], currentStamp[0] + 1)
总结: AtomicStampedReference和 AtomicMarkableReference是通过版本号(时间戳)来解决ABA问题的,我们也可以使用版本号(verison)来解决ABA。
即乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败。
四、java.util.concurrent.atomic.AtomicStampedReference的源代码是如何实现的
1. 创建一个Pair类来记录对象引用和时间戳信息,采用int作为时间戳,实际使用的时候时间戳信息要做成自增的,否则时间戳如果重复,还会出现ABA的问题。这个Pair对象是不可变对象,所有的属性都是final的, of方法每次返回一个新的不可变对象。
2. 使用一个volatile类型的引用指向当前的Pair对象,一旦volatile引用发生变化,变化对所有线程可见。
3. set方法时,当要设置的对象和当前Pair对象不一样时,新建一个不可变的Pair对象。
4. compareAndSet方法中,只有期望对象的引用和版本号和目标对象的引用和版本好都一样时,才会新建一个Pair对象,然后用新建的Pair对象和原理的Pair对象做CAS操作。
5. 实际的CAS操作比较的是当前的pair对象和新建的pair对象,pair对象封装了引用和时间戳信息。
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
private volatile Pair<V> pair;
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
public void set(V newReference, int newStamp) {
Pair<V> current = pair;
if (newReference != current.reference || newStamp != current.stamp)
this.pair = Pair.of(newReference, newStamp);
}
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
private static final long pairOffset =
objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
CAS和ABA问题的更多相关文章
- 沉淀再出发:java中的CAS和ABA问题整理
沉淀再出发:java中的CAS和ABA问题整理 一.前言 在多并发程序设计之中,我们不得不面对并发.互斥.竞争.死锁.资源抢占等等问题,归根到底就是读写的问题,有了读写才有了增删改查,才有了所有的一切 ...
- java并发编程(十三)----(JUC原子类)引用类型介绍(CAS和ABA的介绍)
这一节我们将探讨引用类型原子类:AtomicReference, AtomicStampedRerence, AtomicMarkableReference.AtomicReference的使用非常简 ...
- CAS及其ABA问题
CAS.volatile是JUC包实现同步的基础.Synchronized下的偏向锁.轻量级锁的获取.释放,lock机制下锁的获取.释放,获取失败后线程的入队等操作都是CAS操作锁标志位.state. ...
- Java高性能编程之CAS与ABA及解决方法
Java高性能编程之CAS与ABA及解决方法 前言 如果喜欢暗色调的界面或者想换换界面,可以看看我在个人博客发布的 Java高性能编程之CAS与ABA及解决方法. CAS概念 CAS,全称Compar ...
- CAS的ABA问题详解
CAS的ABA问题详解 ABA问题 在多线程场景下CAS会出现ABA问题,关于ABA问题这里简单科普下,例如有2个线程同时对同一个值(初始值为A)进行CAS操作,这三个线程如下 1.线程1,期望值为A ...
- CAS 和 ABA 问题
CAS简介 CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制. CAS 它是一条CPU并发原语.操作包含三个操作数 -- 内存位置.预期数值和新值.CAS ...
- Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)
摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...
- Java CAS 和ABA问题
独占锁:是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁. 乐观锁:每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功 ...
- 无锁编程(四) - CAS与ABA问题
CAS 一般采用原子级的read-modify-write原语来实现Lock-Free算法,其中LL和SC是Lock-Free理论研究领域的理想原语,但实现这些原语需要CPU指令的支持,非常遗憾的是目 ...
- 谈论高并发(十二)分析java.util.concurrent.atomic.AtomicStampedReference看看如何解决源代码CAS的ABA问题
于谈论高并发(十一)几个自旋锁的实现(五岁以下儿童)中使用了java.util.concurrent.atomic.AtomicStampedReference原子变量指向工作队列的队尾,为何使用At ...
随机推荐
- linux 系统进程理解
1.为了对进程从产生到消亡的整个过程进行跟踪和描述,就需要定义各种进程的各种状态并制定相应的状态转换策略,以此来控制进程的运行. 不同的操作系统对进程的状态解释不同,但是最基本的状态都是一样 ...
- Zabbix3.0.4监控Windows的CPU使用百分比并在CPU使用率超过90%触发报警
Zabbix3.0.4监控Windows的CPU使用百分比 Zabbix 自带的模块没有 CPU 使用率(百分比)这个监控项,我们可以通过添加计数器的方式实现 CPU 百分比的监控. 1.在Zabbi ...
- js -- sort() 使用排序函数
JavaScript sort() 方法 JavaScript Array 对象 定义和用法 sort() 方法用于对数组的元素进行排序. 语法 arrayObject.sort(sortby) 参数 ...
- mac 上如何安装非app store上的下载的软件-------打开未知来源
打开了 Terminal 终端后 ,在命令提示后输入 sudo spctl --master-disable 并按下回车执行,如下图所示. 随后再输入当前 Mac 用户的密码,如下图所示. 如 ...
- CentOS7安装和配置MySQL
1. 下载MySQL Yum Repository http://dev.mysql.com/downloads/repo/yum/ 2. 本地安装MySQL Yum Repository s ...
- 牛客网 python 求解立方根
•计算一个数字的立方根,不使用库函数 详细描述: •接口说明 原型: public static double getCubeRoot(double input) 输入:double 待求解参数 返回 ...
- 处理:“ORA-00257: archiver error. Connect internal only, until freed”的错误问题
注:本文参考了< ORA-00257: archiver error. Connect internal only, until freed 错误的处理方法 > 一:问题背景: 今天在 ...
- jdbctemplate 调用oracle 有返回(会话型临时表数据的)结果的存储过程
注:本文为博主 原创. jdbctemplate 调用oracle存储过程 事务 临时表 有返回结果 1:java 代码 本逻辑代码本是想把 java 代码里的list<Strign>类 ...
- Vmware Workstation _linux yum 仓库搭建
0:检查 vm虚拟机光盘是否已经连接 1. 检测yum 仓库是否已经配置好 [root@oracle ~]# yum list all 如果输入这条指令可以正确显示出rpm 包的列表,则说明yum 仓 ...
- Java的小实验——各种测试以及说明
日期:2018.10.07 星期五 博客期:014 一.Java中的位运算 代码如下: package Morts107; public class Test107 { public static v ...