Java CAS 和ABA问题
2019独角兽企业重金招聘Python工程师标准>>>
独占锁:是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
乐观锁:每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。
一、CAS 操作
乐观锁用到的机制就是CAS,Compare and Swap。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
1、非阻塞算法 (nonblocking algorithms)
一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。
2、AtomicInteger示例
拿出AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。
private volatile int value;
在没有锁的机制下需要借助volatile原语,保证线程间的数据是可见的(共享的)。
这样才获取变量的值的时候才能直接读取。
public final int get() {return value; }
然后来看看 ++i 是怎么做到的。
public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;} }
在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。
而compareAndSet利用JNI来完成CPU指令的操作。
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。
而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。参考资料的文章中介绍了如果利用CAS构建非阻塞计数器、队列等数据结构。
二、ABA问题
CAS看起来很爽,但是会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。这允许一对变化的元素进行原子操作。
在运用CAS做Lock-Free操作中有一个经典的ABA问题:
线程1准备用CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题,例如下面的例子:
现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:
head.compareAndSet(A,B);
在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A,此时堆栈结构如下图,而对象B此时处于游离状态:
此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,所以此时的情况变为:
其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。
以上就是由于ABA问题带来的隐患,各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题,在Java中,AtomicStampedReference<E>也实现了这个作用,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题,例如下面的代码分别用AtomicInteger和AtomicStampedReference来对初始值为100的原子整型变量进行更新,AtomicInteger会成功执行CAS操作,而加上版本戳的AtomicStampedReference对于ABA问题会执行CAS失败:
package concur.lock;import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicStampedReference;public class ABA {private static AtomicInteger atomicInt = new AtomicInteger(100);private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100, 0);public static void main(String[] args) throws InterruptedException {Thread intT1 = new Thread(new Runnable() {@Overridepublic void run() {atomicInt.compareAndSet(100, 101);atomicInt.compareAndSet(101, 100);}});Thread intT2 = new Thread(new Runnable() {@Overridepublic void run() {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}boolean c3 = atomicInt.compareAndSet(100, 101);System.out.println(c3); //true}});intT1.start();intT2.start();intT1.join();intT2.join();Thread refT1 = new Thread(new Runnable() {@Overridepublic void run() {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);}});Thread refT2 = new Thread(new Runnable() {@Overridepublic void run() {int stamp = atomicStampedRef.getStamp();System.out.println("before sleep : stamp = " + stamp); // stamp = 0try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("after sleep : stamp = " + atomicStampedRef.getStamp());//stamp = 1boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp+1);System.out.println(c3); //false}});refT1.start();refT2.start();}}
转载于:https://my.oschina.net/u/1185936/blog/840059
Java CAS 和ABA问题相关推荐
- java购买同一件商品时加锁_java中CAS的ABA问题思考和整理(不看后悔系列)
前言 听说经常面试被问到~今天同事说了这个问题,就查了一下这问题,觉得挺有意思的,就整理出来跟大家分享下.主要思考下面几个问题: 1.什么是CAS? 2.什么是CAS的ABA问题? 3.怎么解决这个问 ...
- Java CAS 原理分析
1.简介 CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制.CAS 操作包含三个操作数 – 内存位置.预期数值和新值.CAS 的实现逻辑是将内存位置处的数值 ...
- 死磕java并发cas_死磕Java——CAS
一.死磕Java--CAS 前面我们说到volatile不保证原子性,解决办法就是使用AtomicInteger代替int,但是为什么使用AtomicInteger就可以保证了原子性了,是因为Atom ...
- CAS的ABA问题描述 AtomicStampReference
CAS的ABA问题描述 在CAS操作的时候,其他线程将当前变量的值从A改成B,又改回A: CAS线程用期望值A与当前变量比较的时候,发现当前变量没有变,于是CAS就将当前变量进行了交换操作,但其实当前 ...
- atomic原子类实现机制_并发编程:并发操作原子类Atomic以及CAS的ABA问题
本文基于JDK1.8 Atomic原子类 原子类是具有原子操作特征的类. 原子类存在于java.util.concurrent.atmic包下. 根据操作的数据类型,原子类可以分为以下几类. 基本类型 ...
- cas内外网同时访问_科普:CAS 和 ABA
CAS简介 CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制. CAS 它是一条CPU并发原语.操作包含三个操作数 -- 内存位置.预期数值和新值.CAS ...
- AtomicStampedReference解决CAS的ABA问题
AtomicStampReference 解决CAS的ABA问题 什么是ABA ABA问题:指CAS操作的时候,线程将某个变量值由A修改为B,但是又改回了A,其他线程发现A并未改变,于是CAS将进行值 ...
- mysql解决aba问题_AtomicStampedReference解决CAS的ABA问题
AtomicStampReference > 解决CAS的ABA问题 什么是ABA > ABA问题:指CAS操作的时候,线程将某个变量值由A修改为B,但是又改回了A,其他线程发现A并未改变 ...
- java cas 和AQS
CAS(Compare And Swap)原理分析 字面意思是比较和交换,先看看下面场景(A 和 B 线程同时执行下面的代码): int i = 10; //代码 1 i = 20; //代码 2 复 ...
最新文章
- 2018年全球自动驾驶法律政策研究 | 附报告下载
- 转换Word文档为PDF文件
- 清华体质优良可降5分录取;窃取密钥者奖百万;阿里投入1亿保护方言;腾讯不正当竞争被罚;这就是今天的大新闻...
- 解决log4j:WARN Error initializing output writer. log4j:WARN Unsupported encoding?的问题
- python3.7.2安装pywifi_在Win10中安装pywifi包
- python的os,commands,subprocess启动进程调用的几种方法
- Codeforces Round #565 (div. 3)
- 积木赛尔号机器人_【点映观影】赛尔号大电影7:疯狂机器城赛尔宇宙探险迎人类首秀...
- 视频演示 | 功能新增 | 增强版在线LEFSe分析和可视化鉴定标志性基因或物种
- Git之(三)辅助命令
- python3分解质因数_Python 质因数分解的研究
- 常用的javascript实例
- python将图片转动漫_如何将照片动漫化
- 根据年月日计算今天是一年中的第几天
- Mr.Xiong使用jQuery从控制器获取数据
- 一场属于HTML5守望者的盛宴——记图灵参加HTML5峰会
- word文档格式化要求
- 《电路基础》反相运算放大器
- 【原】WPF按钮或page获取焦点后,按backspace键,后退的解决方法
- Word编辑技巧(图片、文本框居中)