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 ...
随机推荐
- Spring MVC的核心控制器DispatcherServlet的作用
关于Spring MVC的核心控制器DispatcherServlet的作用,以下说法错误的是( )? 它负责接收HTTP请求 加载配置文件 实现业务操作 初始化上下应用对象ApplicationC ...
- 035_lua快速入门
执行下面的脚本用luajit test.lua即可 一.变量及逻辑运算 --number, string, boolean, table, function, thread, userdata, ni ...
- C# 获取区域和语言值
其他方法如 System.Globalization.CultureInfo.InstalledUICulture.Name == "zh-CN" 不能获取.只有通过读注册表的方法 ...
- Android视频录制命令screenrecord
不管是教学,还是为了演示,如果能将Android手机(或平板)的屏幕录制成视频文件,那是一件非常酷的事(iOS8已经提供了这一功能,能通过OS X直接在Mac上录制iPad.iPhone的屏幕,win ...
- swift 实践- 08 -- UISegmentedControl
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoa ...
- 使用open live writer客户端写博客
注:Windows Live Writer 已经停止更新,建议安装 Open Live Writer,下载地址: http://openlivewriter.org/ 使用open live writ ...
- Confluence 6 手动备份
Confluence 的 Attachment Storage Configuration 可以配置 Confluence 将附件存储在 home directory,或者是存储在数据库中. Dat ...
- IOS 命令行工具开发
例子 我们需要查看手机APP里面的某个应用的架构 新建一个Single View App 的ios项目 ToolCL 然后在 main函数中加入以下代码 // // main.m // ToolCL ...
- java接口实现
1.接口中的方法一定是public abstract方法所以类要继承实现接口的时候,一定要去掉abstract修饰符,而且还要标明方法的访问权限一定是public 声明接口不适用public就是友好的 ...
- Linux----centos安装mysql
第一步wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm 第二步rpm -ivh mysql-community-r ...