CAS 导致 ABA 问题
CAS 算法实现了一个重要的前提,需要取出内存中某时刻的数据,并在当下时刻比较并替换,那么这个时间差会导致数据的变化。

比如说一个线程 one 从内存位置 V 中取出A,这时候另外一个线程 two 也从内存中取出 A,并且线程 two进行了一些操作将值变成了B,然后线程 two 又将 V 位置的数据变成 A,这时候线程 one 进行CAS操作发现内存中仍然是 A,然后线程 one 操作成功 。尽管线程 one 的 CAS 操作成功,但是不代表这个过程就是没有问题的

原子引用

原子引用其实和原子包装类是差不多的概念,就是将一个 java 类,用原子引用类进行包装起来,那么这个类就具备了原子性 。

@Data
@ToString
class User {
String userName;
int age;
}
public class AtomicReferenceDemo { public static void main(String[] args) { User z3 = new User("z3", 22); User l4 = new User("l4", 25); // 创建原子引用包装类
AtomicReference<User> atomicReference = new AtomicReference<>(); // 现在主物理内存的共享变量为z3
atomicReference.set(z3); // 比较并交换,如果现在主物理内存的值为z3,那么交换成l4
System.out.println(atomicReference.compareAndSet(z3, l4) + "\t " + atomicReference.get().toString()); // 比较并交换,现在主物理内存的值是l4了,但是预期为z3,因此交换失败
System.out.println(atomicReference.compareAndSet(z3, l4) + "\t " + atomicReference.get().toString());
}
}

基于原子引用的 ABA 问题

我们首先创建了两个线程,然后 T1 线程,执行一次 ABA 的操作,T2 线程在一秒后修改主内存的值

public class ABADemo {

    /**
* 普通的原子引用包装类
*/
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); public static void main(String[] args) { new Thread(() -> {
// 把100 改成 101 然后在改成100,也就是ABA
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start(); new Thread(() -> {
try {
// 睡眠一秒,保证t1线程,完成了ABA操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 把100 改成 101 然后在改成100,也就是ABA
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get()); }, "t2").start();
}
}

运行后成功的修改了,这就是 ABA 问题

解决 ABA 问题
在java的并发包下,有AtomicStampedReference这个类,可以解决ABA问题。其原理其实就是新增一种机制,修改增加版本号,类似于时间戳 的概念

T1: 100 1 2019 2

T2: 100 1 101 2 100 3

如果 T1 修改的时候,版本号为 2,落后于现在的版本号 3,所以要重新获取最新值,这里就提出了一个使用时间戳版本号,来解决 ABA 问题的思路 。

AtomicStampedReference
时间戳原子引用,来这里应用于版本号的更新,也就是每次更新的时候,需要比较期望值和当前值,以及期望版本号和当前版本号 。

public class ABADemo {

    /**
* 普通的原子引用包装类
*/
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); // 传递两个值,一个是初始值,一个是初始版本号
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1); public static void main(String[] args) { System.out.println("============以下是ABA问题的产生=========="); new Thread(() -> {
// 把100 改成 101 然后在改成100,也就是ABA
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start(); new Thread(() -> {
try {
// 睡眠一秒,保证t1线程,完成了ABA操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 把100 改成 101 然后在改成100,也就是ABA
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get()); }, "t2").start(); System.out.println("============以下是ABA问题的解决=========="); new Thread(() -> { // 获取版本号
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp); // 暂停t3一秒钟
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} // 传入4个值,期望值,更新值,期望版本号,更新版本号
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName() + "\t 第二次版本号" + atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName() + "\t 第三次版本号" + atomicStampedReference.getStamp()); }, "t3").start(); new Thread(() -> { // 获取版本号
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp); // 暂停t4 3秒钟,保证t3线程也进行一次ABA问题
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp+1); System.out.println(Thread.currentThread().getName() + "\t 修改成功否:" + result + "\t 当前最新实际版本号:" + atomicStampedReference.getStamp()); System.out.println(Thread.currentThread().getName() + "\t 当前实际最新值" + atomicStampedReference.getReference()); }, "t4").start(); }
}

我们能够发现,线程 t3,在进行 ABA 操作后,版本号变更成了 3,而线程 t4 在进行操作的时候,就出现操作失败了,因为版本号和当初拿到的不一样 。

ABA 问题的更多相关文章

  1. 装逼名词-ABA CAS SpinLock

    今天看wiki,看到一个提到什么什么会陷入 race condition & ABA problem.丫的我没听过ABA呀,那么我去搜了一下,如下: http://www.bubuko.com ...

  2. 并发中的Native方法,CAS操作与ABA问题

    Native方法,Unsafe与CAS操作 >>JNI和Native方法 Java中,通过JNI(Java Native Interface,java本地接口)来实现本地化,访问操作系统底 ...

  3. 无锁队列以及ABA问题

    队列是我们非常常用的数据结构,用来提供数据的写入和读取功能,而且通常在不同线程之间作为数据通信的桥梁.不过在将无锁队列的算法之前,需要先了解一下CAS(compare and swap)的原理.由于多 ...

  4. Java CAS 和ABA问题

    独占锁:是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁. 乐观锁:每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功 ...

  5. 无锁编程(四) - CAS与ABA问题

    CAS 一般采用原子级的read-modify-write原语来实现Lock-Free算法,其中LL和SC是Lock-Free理论研究领域的理想原语,但实现这些原语需要CPU指令的支持,非常遗憾的是目 ...

  6. SpinLock 自旋锁, CAS操作(Compare & Set) ABA Problem

    SpinLock 自旋锁 spinlock 用于CPU同步, 它的实现是基于CPU锁定数据总线的指令. 当某个CPU锁住数据总线后, 它读一个内存单元(spinlock_t)来判断这个spinlock ...

  7. ABA problem

    多线程及多进程编程同步时可能出现的问题,如果一个值被P1读取两次,两次的值相同,据此判断该值没有被修改过,但该值可能在两次读取之间被P2修改为另外一个value,并在P1再次读取之前修改回了原值.P1 ...

  8. JAVA与ABA问题

    在<JAVA并发编程实战>的第15.4.4节中看到了一些关于ABA问题的描述.有一篇文章摘录了书里的内容. 书中有一段内容为: 如果在算法中采用自己的方式来管理节点对象的内存,那么可能出现 ...

  9. 一个可无限伸缩且无ABA问题的无锁队列

    关于无锁队列,详细的介绍请参考陈硕先生的<无锁队列的实现>一文.然进一步,如何实现一个不限node数目即能够无限伸缩的无锁队列,即是本文的要旨. 无锁队列有两种实现形式,分别是数组与链表. ...

  10. 谈论高并发(十二)分析java.util.concurrent.atomic.AtomicStampedReference看看如何解决源代码CAS的ABA问题

    于谈论高并发(十一)几个自旋锁的实现(五岁以下儿童)中使用了java.util.concurrent.atomic.AtomicStampedReference原子变量指向工作队列的队尾,为何使用At ...

随机推荐

  1. 集合栈 牛客网 程序员面试金典 C++ Python

    集合栈 牛客网 程序员面试金典 C++ Python 题目描述 请实现一种数据结构SetOfStacks,由多个栈组成,其中每个栈的大小为size,当前一个栈填满时,新建一个栈.该数据结构应支持与普通 ...

  2. TypeError: 'encoding' is an invalid keyword argument for this function 解决Python 2.7

    在python2.7中这样调用代码 open('file/name.txt','r',encoding= 'utf-8').read() 会出现 TypeError: 'encoding' is an ...

  3. JS基础面试

    1. JS是高级语言弱类型语言 脚本语言 1.1高级语言我们写完的代码不能直接执行,要先经过js引擎翻译成0101这种机器语言才能执行 1.2 弱类型语言变量可以在前一行设置为一个数字,下一行修改为一 ...

  4. DeWeb进阶 :控件开发 --- 1 完成一个纯html的demo

    最近随着DeWeb(以下简称DW)的完善,和群友的应用的深入,已经有网友开始尝试做DeWeb支持控件的开发了! 这太令人兴奋了! 作为DeWeb的开发者,感觉DeWeb的优势之一就是简洁的第三方控件扩 ...

  5. 批量免密ssh

    参考连接:https://www.cnblogs.com/xiaoyuxixi/p/11413355.html 适用于所有密码都一样的情况下 应用场景: 在应用ansible的实际情况中,有一个很现实 ...

  6. ESP32-IDF安装并在VSCode上编译Hello World

    ESP32-IDF安装 准备工作 安装python 3 安装方法参考链接:https://blog.csdn.net/hg_qry/article/details/106415252 安装git 安装 ...

  7. Linux 权限控制

    权限管理概述 为什么要进行权限管理? 因为在生产服务器上,如果要让普通用户登录,就要给他分配合理的权限,在服务器上需要为用户严格定义权限等级,否则如果所有人都是roσt权限,权限过高容易导致岀现误操作 ...

  8. springboot注解之容器功能

    添加组件 @Configuration.@Bean //以swagger为例 @Configuration(proxyBeanMethods = false) @EnableSwagger2 //使用 ...

  9. 谷粒 | 项目集成redis

    添加依赖 由于redis缓存是公共应用,所以我们把依赖与配置添加到了common模块下面,在common模块pom.xml下添加以下依赖 <!-- redis --> <depend ...

  10. uni-app视频组件设置圆角

    无法实现,建议写个image在中间位置加个播放按钮,点击播放跳转新页面只需要在跳转参数里面把视频链接加上,在onLoad里面获取视频链接,自动播放视频,很多app目前都是这样做的,关闭页面后视频会自动 ...