原子类AtomicInteger的ABA问题

连环套路

从AtomicInteger引出下面的问题

CAS -> Unsafe -> CAS底层思想 -> ABA -> 原子引用更新 -> 如何规避ABA问题

ABA问题是什么

狸猫换太子

假设现在有两个线程,分别是T1 和 T2,然后T1执行某个操作的时间为10秒,T2执行某个时间的操作是2秒,最开始AB两个线程,分别从主内存中获取A值,但是因为B的执行速度更快,他先把A的值改成B,然后在修改成A,然后执行完毕,T1线程在10秒后,执行完毕,判断内存中的值为A,并且和自己预期的值一样,它就认为没有人更改了主内存中的值,就快乐的修改成B,但是实际上 可能中间经历了 ABCDEFA 这个变换,也就是中间的值经历了狸猫换太子。

所以ABA问题就是,在进行获取主内存值的时候,该内存值在我们写入主内存的时候,已经被修改了N次,但是最终又改成原来的值了

CAS导致ABA问题

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

比如说一个线程one从内存位置V中取出A,这时候另外一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功

尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的

ABA问题

CAS只管开头和结尾,也就是头和尾是一样,那就修改成功,中间的这个过程,可能会被人修改过

原子引用

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

  1. /**
  2. * 原子类引用
  3. */
  4. @Data
  5. @AllArgsConstructor
  6. class User {
  7. String userName;
  8. int age;
  9. }
  10. public class AtomicReferenceDemo {
  11. public static void main(String[] args) {
  12. User aaa = new User("aaa", 20);
  13. User bbb = new User("bbb", 30);
  14. // 创建原子引用包装类
  15. AtomicReference<User> atomicReference = new AtomicReference<>();
  16. // 现在主物理内存的共享变量,为aaa
  17. atomicReference.set(aaa);
  18. // 比较并交换,如果现在主物理内存的值为aaa,那么交换成bbb
  19. System.out.println(atomicReference.compareAndSet(aaa, bbb) + "\t " + atomicReference.get().toString());
  20. // 比较并交换,现在主物理内存的值是bbb了,但是预期为aaa,因此交换失败
  21. System.out.println(atomicReference.compareAndSet(aaa, bbb) + "\t " + atomicReference.get().toString());
  22. }
  23. }

基于原子引用的ABA问题

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

  1. /**
  2. * 基于CAS引出ABA问题
  3. */
  4. public class ABADemo {
  5. static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);
  6. public static void main(String[] args) {
  7. new Thread(()->{
  8. // 把100 改成 127 然后在改成100,也就是ABA
  9. atomicReference.compareAndSet(100, 127);
  10. //特别强调在AtomicReference(Integer)中value超出-128~127,会生成一个新的对象而造成无法修改
  11. //但是在AtomicInteger中则不会存在这样的问题
  12. atomicReference.compareAndSet(127, 100);
  13. },"t1").start();
  14. new Thread(()->{
  15. try {
  16. // 睡眠一秒,保证t1线程,完成了ABA操作
  17. TimeUnit.SECONDS.sleep(1);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. // 把100 改成 127 然后在改成100,也就是ABA
  22. System.out.println(atomicReference.compareAndSet(100, 2021)+"\t"+atomicReference.get());
  23. },"t2").start();
  24. }
  25. }

我们发现,它能够成功的修改,这就是ABA问题

解决ABA问题

新增一种机制,也就是修改版本号,类似于时间戳的概念

T1: 100 1 2020 2

T2: 100 1 127 2 100 3

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

AtomicStampedReference

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

  1. /**
  2. * 基于CAS引出ABA问题并采用AtomicStampedReference解决
  3. */
  4. public class ABADemo {
  5. static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);
  6. static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
  7. public static void main(String[] args) {
  8. System.out.println("============以下是ABA问题的产生==========");
  9. new Thread(() -> {
  10. // 把100 改成 101 然后在改成100,也就是ABA
  11. atomicReference.compareAndSet(100, 127);
  12. atomicReference.compareAndSet(127, 100);
  13. }, "t1").start();
  14. new Thread(() -> {
  15. try {
  16. // 睡眠一秒,保证t1线程,完成了ABA操作
  17. TimeUnit.SECONDS.sleep(1);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. // 把100 改成 101 然后在改成100,也就是ABA
  22. System.out.println(atomicReference.compareAndSet(100, 2020) + "\t" + atomicReference.get());
  23. }, "t2").start();
  24. //main线程和gc线程之外如果还有线程就处于等待
  25. while (Thread.activeCount() > 2) {
  26. Thread.yield();
  27. }
  28. System.out.println("============以下是ABA问题的解决==========");
  29. new Thread(() -> {
  30. // 获取版本号
  31. int stamp = atomicStampedReference.getStamp();
  32. System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp);
  33. // 暂停t3一秒钟
  34. try {
  35. TimeUnit.SECONDS.sleep(1);
  36. } catch (InterruptedException e) {
  37. e.printStackTrace();
  38. }
  39. // 传入4个值,期望值,更新值,期望版本号,更新版本号
  40. atomicStampedReference.compareAndSet(100, 127, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
  41. System.out.println(Thread.currentThread().getName() + "\t 第二次版本号" + atomicStampedReference.getStamp());
  42. atomicStampedReference.compareAndSet(127, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
  43. System.out.println(Thread.currentThread().getName() + "\t 第三次版本号" + atomicStampedReference.getStamp());
  44. }, "t3").start();
  45. new Thread(() -> {
  46. // 获取版本号
  47. int stamp = atomicStampedReference.getStamp();
  48. System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp);
  49. // 暂停t4 3秒钟,保证t3线程也进行一次ABA问题
  50. try {
  51. TimeUnit.SECONDS.sleep(3);
  52. } catch (InterruptedException e) {
  53. e.printStackTrace();
  54. }
  55. boolean result = atomicStampedReference.compareAndSet(100, 2020, stamp, stamp+1);
  56. System.out.println(Thread.currentThread().getName() + "\t 修改成功否:" + result + "\t 当前最新实际版本号:" + atomicStampedReference.getStamp());
  57. System.out.println(Thread.currentThread().getName() + "\t 当前实际最新值" + atomicStampedReference.getReference());
  58. }, "t4").start();
  59. }
  60. }

运行结果为:

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

原子类的ABA问题的更多相关文章

  1. 原子类的 ABA 问题

    原子引用 public class AtomicReferenceDemo { public static void main(String[] args) { User cuzz = new Use ...

  2. java并发编程(十三)----(JUC原子类)引用类型介绍(CAS和ABA的介绍)

    这一节我们将探讨引用类型原子类:AtomicReference, AtomicStampedRerence, AtomicMarkableReference.AtomicReference的使用非常简 ...

  3. java的原子类到底是啥?ABA,CAS又是些什么?

    1)解决并发不是用锁就能解决吗,那SDK干嘛还要搞个原子类出来? 锁虽然能解决,但是加锁解锁始终还是对性能是有影响的,并且使用不当可能会造成死锁之类的问题. 2)原子类是怎样使用的,比如说我要实现一个 ...

  4. 深入浅出Java并发包—原子类操作

    我们知道,JDK1.5以后引入了并发包(java.util.concurrent)用于解决多CPU时代的并发问题,而并发包中的类大部分是基于Queue的并发类,Queue在大多数情况下使用了原子类(A ...

  5. 对Java原子类AtomicInteger实现原理的一点总结

    java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...

  6. Android并发编程 原子类与并发容器

    在Android开发的漫漫长途上的一点感想和记录,如果能给各位看官带来一丝启发或者帮助,那真是极好的. 前言 上一篇博文中,主要说了些线程以及锁的东西,我们大多数的并发开发需求,基本上可以用synch ...

  7. Java原子类AtomicInteger实现原理的一点总结

    java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...

  8. Java原子类实现原理分析

    在谈谈java中的volatile一文中,我们提到过并发包中的原子类可以解决类似num++这样的复合类操作的原子性问题,相比锁机制,使用原子类更精巧轻量,性能开销更小,本章就一起来分析下原子类的实现机 ...

  9. Java并发—原子类,java.util.concurrent.atomic包(转载)

    原子类 Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中 的原子操作类提供了一种用法简单.性能高效.线程安全地更新一个变量 ...

随机推荐

  1. 浅尝Go语言GC

    大家好,我是小栈君,因为个人和工作的缘故,所以拖更了一点时间,但是关于拖更的内容小栈君会在后续的时间中补回来,还希望大家继续支持和关注小栈君.当然,在国内疫情稍微减缓的情况下,小栈君在这里也多说两句, ...

  2. 如何测试Linux命令运行时间?

    良许在工作中,写过一个 Shell 脚本,这个脚本可以从 4 个 NTP 服务器轮流获取时间,然后将最可靠的时间设置为系统时间. 因为我们对于时间的要求比较高,需要在短时间内就获取到正确的时间.所以我 ...

  3. Ubuntu下已安装Anaconda但出现conda: command not found错误解决办法

    原因:环境未配置 执行[vim ~/.bashrc]命令,进入配置文件,在最后一行按'o'插入一行,并添加语句: export PATH=/home/duanyongchun/anaconda3/bi ...

  4. 【webpack 系列】进阶篇

    本文将继续引入更多的 webpack 配置,建议先阅读[webpack 系列]基础篇的内容.如果发现文中有任何错误,请在评论区指正.本文所有代码都可在 github 找到. 打包多页应用 之前我们配置 ...

  5. Java 类加载器解析及常见类加载问题

    Java 类加载器解析及常见类加载问题 java.lang.ClassLoader 每个类加载器本身也是个对象--一个继承 java.lang.ClassLoader 的实例.每个类被其中一个实例加载 ...

  6. 听说你还搞不定java中的==和equals?

    相信很多读者关于==和equals懂了又懵,懵了又懂,如此循环,事实上可能是因为看到的博客文章之类的太多了,长篇大论,加上一段时间的洗礼之后就迷路了.本篇文章再一次理清楚.当然如果觉得本文太啰嗦的话, ...

  7. NKOJ3765 k个最小和

    问题描述 有k个整数数组,各包含k个元素,从每个数组中选取一个元素加起来,可以得到k^k个和,求这些和中最小的k个值. 输入格式 第一行,一个整数k(k<=500)接下来k行,每行k个正整数(& ...

  8. 错误:Attempt to resolve method: [XXX方法] on undefined variable or class name: [XXX类]的解决(IDEA打包jar问题)

    问题: 使用JMeter调用jar包的时候,报错误信息Typed variable declaration : Attempt to resolve method:[XXX方法] on undefin ...

  9. 使用gulp搭建一个传统的多页面前端项目的开发环境

    1.简介 使用gulp搭建一个传统的多页面前端项目的开发环境 支持pug scss es6编译支持 支持开发环境和打包生成sourceMap 支持文件变动自动刷新浏览器,css是热更新(css改动无需 ...

  10. CentOS 6.5系统实现NFS文件共享

    一台Linux server ip 192.168.1.254,一台Linux client ip 192.168.1.100操作系统:CentOS 6.5需求描述:1:将/root 共享给192.1 ...