【原创】 强哥Java架构之路 2019-05-26 07:00:00

通过对atomic包的分析我们知道了CAS机制,我们在看一下CAS的公式。

CAS(V,A,B)
1:V表示内存中的地址
2:A表示预期值
3:B表示要修改的新值

CAS的原理就是预期值A与内存中的值相比较,如果相同则将内存中的值改变成新值B。这样比较有两类:

第一类:如果操作的是基本变量,则比较的是 值 是否相等。

第二类:如果操作的是对象的引用,则比较的是对象在 内存的地址 是否相等。

总结一句话就是:比较并交换。

其实CAS是Java乐观锁的一种实现机制,在Java并发包中,大部分类就是通过CAS机制实现的线程安全,它不会阻塞线程,如果更改失败则可以自旋重试,但是它也存在很多问题:

1:ABA问题,也就是说从A变成B,然后就变成A,但是并不能说明其他线程并没改变过它,利用CAS就发现不了这种改变。
2:由于CAS失败后会继续重试,导致一致占用着CPU。

用一个图来说明ABA的问题。

线程1准备利用CAS修改变量值A,但是在修改之前,其他线程已经将A变成了B,然后又变成A,即A->B->A,线程1执行CAS的时候发现仍然为A,所以CAS会操作成功,但是其实目前这个A已经是其他线程修改的了,但是线程1并不知道,最终内存值变成了B,这就导致了ABA问题。

接下来我们看一个关于ABA的例子:

public class AtomicMarkableReferenceTest {private final static String A = "A";private final static String B = "B";private final static AtomicReference<String> ar = new AtomicReference<>(A);public static void main(String[] args) {new Thread(() -> {try {Thread.sleep(Math.abs((int) (Math.random() * 100)));} catch (InterruptedException e) {e.printStackTrace();}if (ar.compareAndSet(A, B)) {System.out.println("我是线程1,我成功将A改成了B");}}).start();new Thread(() -> {if (ar.compareAndSet(A, B)) {System.out.println("我是线程2,我成功将A改成了B");}}).start();new Thread(() -> {if (ar.compareAndSet(B,A)) {System.out.println("我是线程3,我成功将B改成了A");}}).start();}
}

上面例子运行结果如下,线程1并不知道线程2和线程3已经改过了值,线程1发现此时还是A则会更改成功,这就是ABA:

所以每种技术都有它的两面性,在解决了一些问题的同时也出现了一些新的问题,在JDK中也为我们提供了两种解决ABA问题的方案,接下来我们就看一下是怎样解决的。

本篇文章的主要内容:

1:AtomicMarkableReference 实例和源码解析
2:AtomicStampedReference 实例和源码解析

一、AtomicMarkableReference实例和源码解析

上面的例子如果利用这个类去实现,会怎样呢?稍微改变上面的代码如下:

public class AtomicMarkableReferenceTest {private final static String A = "A";private final static String B = "B";private final static AtomicMarkableReference<String> ar = new AtomicMarkableReference<>(A, false);public static void main(String[] args) {new Thread(() -> {try {Thread.sleep(Math.abs((int) (Math.random() * 100)));} catch (InterruptedException e) {e.printStackTrace();}if (ar.compareAndSet(A, B, false, true)) {System.out.println("我是线程1,我成功将A改成了B");}}).start();new Thread(() -> {if (ar.compareAndSet(A, B, false, true)) {System.out.println("我是线程2,我成功将A改成了B");}}).start();new Thread(() -> {if (ar.compareAndSet(B, A, ar.isMarked(), true)) {System.out.println("我是线程3,我成功将B改成了A");}}).start();}
}

运行结果如下:

是不是解决了这个ABA的问题,AtomicMarkableReference仅仅用一个boolean标记解决了这个问题,那接下来我们进入源码看看它是怎么一种机制。

1:成员变量

private volatile Pair<V> pair;

定义了一个被关键字volatile修饰的Pair,那Pair是什么对象呢?

private static class Pair<T> {
//封装了我们传递的对象final T reference;
//这个就是boolean标记final boolean mark;private Pair(T reference, boolean mark) {this.reference = reference;this.mark = mark;}static <T> Pair<T> of(T reference, boolean mark) {return new Pair<T>(reference, mark);}
}

2:构造函数

public AtomicMarkableReference(V initialRef, boolean initialMark) {pair = Pair.of(initialRef, initialMark);
}

这个构造函数就是调用Pair中的of()方法,把我们需要操作的对象和boolean标记传递进去。

那说明以后的操作都是基于Pair这个类进行操作了。那接下来我们看一下它的CAS方法是怎样定义的。

//expectedReference表示我们传递的预期值
//newReference表示将要更改的新值
//expectedMark表示传递的预期boolean类型标记
//newMark表示将要更改的boolean类型标记的新值。
public boolean compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) {Pair<V> current = pair;returnexpectedReference == current.reference && expectedMark == current.mark
&& ((newReference == current.reference && newMark == current.mark) || casPair(current, Pair.of(newReference,  newMark)));
}

上面的return后的代码分解后主要有三大逻辑:

第一个逻辑&&(第二个逻辑 || 第三个逻辑)

第一个逻辑:预期对象和预期的boolean类型标记必须和内部的Pair中相等

 expectedReference == current.reference && expectedMark == current.mark 

如果第一个逻辑是true,才能继续往下判断,否则直接返回false。

第二个逻辑:如果这个逻辑为true,就不在执行第三个逻辑了

newReference == current.reference && newMark == current.mark

如果新的将要更改的对象和新的将要更改的boolean类型的标记和内部Pair的相等,则就不在执行第三个逻辑了。如果为false,则继续往下执行第三个逻辑

第三个逻辑:CAS逻辑

casPair(current, Pair.of(newReference, newMark))

如果预期的对象和预期的boolean标记和Pair都相等,但是新的对象和新的boolean标记和Pair不相等,此时需要进行CAS更新了

从上面的讲解大家能不能总结出来它是怎样解决ABA的问题的,现在我们总结以下:

它是通过把操作的对象和一个boolean类型的标记封装成Pair,而Pair有被volatile修饰,说明只要更改其他线程立刻可见,而只有Pair中的两个成员变量都相等,来解决CAS中ABA的问题的。一个伪流程图如下:

二、AtomicStampedReference实例和源码解析

上面我们知道了AtomicMarkableReference是通过添加一个boolean类型标记和操作的对象封装成Pair来解决ABA问题的,但是如果想知道被操作对象更改了几次,这个类就无法处理了,因为它仅仅用一个boolean去标记,所以AtomicStampedReference就是解决这个问题的,它通过一个int类型标记来代替boolean类型的标记。

上面的例子更改如下:

public class AtomicMarkableReferenceTest {private final static String A = "A";private final static String B = "B";private static AtomicInteger ai = new AtomicInteger(1);private final static AtomicStampedReference<String> ar = new AtomicStampedReference<>(A, 1);public static void main(String[] args) {new Thread(() -> {try {Thread.sleep(Math.abs((int) (Math.random() * 100)));} catch (InterruptedException e) {e.printStackTrace();}if (ar.compareAndSet(A, B, 1,2)) {System.out.println("我是线程1,我成功将A改成了B");}}).start();new Thread(() -> {if (ar.compareAndSet(A, B, ai.get(),ai.incrementAndGet())) {System.out.println("我是线程2,我成功将A改成了B");}}).start();new Thread(() -> {if (ar.compareAndSet(B, A, ai.get(),ai.incrementAndGet())) {System.out.println("我是线程3,我成功将B改成了A");}}).start();}
}

运行结果:

1:成员变量

private volatile Pair<V> 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);}
}

这种结果和AtomicMarkableReference中的Pair结构类似,只不过是把boolean类型标记改成了int类型标记。

2:构造函数

public AtomicStampedReference(V initialRef, int initialStamp) {pair = Pair.of(initialRef, initialStamp);
}

3:CAS方法

public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) {Pair<V> current = pair;returnexpectedReference == current.reference &&expectedStamp == current.stamp &&((newReference == current.reference &&newStamp == current.stamp) ||casPair(current, Pair.of(newReference, newStamp)));
}

上面分析了JDK中解决CAS中ABA问题的两种解决方案,他们的原理是相同的,就是添加一个标记来记录更改,两者的区别如下:

1:AtomicMarkableReference 利用一个boolean类型的标记来记录,只能记录它改变过,不能记录改变的次数
2:AtomicStampedReference 利用一个int类型的标记来记录,它能够记录改变的次数。

atomic包中的类已经介绍结束,接下来一篇文章我将对atomic做一个总结,然后就开始Java并发包中lock包进行全面解析。

Java解决CAS机制中ABA问题的方案相关推荐

  1. tcp out of order解决_Java解决CAS机制中ABA问题的方案

    通过对atomic包的分析我们知道了CAS机制,我们在看一下CAS的公式. CAS(V,A,B)1:V表示内存中的地址2:A表示预期值3:B表示要修改的新值 CAS的原理就是预期值A与内存中的值相比较 ...

  2. aba问题mysql_解决CAS机制中ABA问题的AtomicStampedReference详解

    AtomicStampedReference是一个带有时间戳的对象引用,能很好的解决CAS机制中的ABA问题,这篇文章将通过案例对其介绍分析. 一.ABA问题 ABA问题是CAS机制中出现的一个问题, ...

  3. CAS机制中的ABA问题

    在进行CAS操作的时候,会检查值有没有发生变化.如果没有变化则更新.如果发生变化了,则进行自旋. 但是有种情况是,预期的值看似没有变化,但是实际上的确变化了,例如原来的值是A,后来变成了B,最后又变成 ...

  4. Java笔记-异常机制中try(...)中括号的用法

    今天在看到某位Java大佬这样的写法,也让大家一起来观摩下: 这里可以看到,将某些变量,和流放到了括号里面,在后面的代码中,也没有finally,也没见将其释放. 经过查阅资料,发现,在java7后有 ...

  5. 真实业务场景展现CAS原理的ABA问题及解决方案

    文章目录 阅读提示 CAS原理.ABA问题介绍 真实业务场景 如何解决ABA问题 CAS学习总结 阅读提示 本文将借助开保险柜的业务场景重点阐述误用AtomicBoolean引起的ABA问题,以及解决 ...

  6. 【Java 并发编程】线程锁机制 ( 悲观锁 | 乐观锁 | CAS 三大问题 | ABA 问题 | 循环时间长问题 | 多个共享变量原子性问题 )

    文章目录 一.悲观锁 二.乐观锁 三.乐观锁 CAS 三大问题 一.悲观锁 假设有 222 个线程 , 线程 A 和 线程 B ; 线程 A 访问共享资源 , 线程 B 等待 , 一旦线程 A 访问结 ...

  7. 详解各种锁:CAS、共享锁、排它锁、互斥锁、悲观锁、乐观锁、行级锁、表级锁、页级锁、死锁、JAVA对CAS的支持、ABA问题、AQS原理

    共享锁(S锁) 又称为读锁,可以查看但无法修改和删除的一种数据锁.如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁.获准共享锁的事务只能读数据,不能修改数据. 共享锁下其它用 ...

  8. cas引出的ABA问题?如何解决?- 理解原子引用、时间戳(版本号)原子引用

    ABA问题: 假如有两个线程1,2: cas下:1.线程取值完等待,2线程取值并把A改成B,有把B改成A,这是1线程执行会任务A还是原来的A没有发生改变,如果不在乎中间结果,只看收尾,那么没必要解决A ...

  9. AtomicStampedReference解决CAS的ABA问题

    AtomicStampReference 解决CAS的ABA问题 什么是ABA ABA问题:指CAS操作的时候,线程将某个变量值由A修改为B,但是又改回了A,其他线程发现A并未改变,于是CAS将进行值 ...

最新文章

  1. Python知识点3——列表操作
  2. java collection join_java – @ElementCollection @CollectionTable在一对多映射中
  3. 链式队列的基本操作(入队、出队、遍历队列、清空队列)
  4. SAP Fiori 1.0 Migrate to Fiori 2.0
  5. stm32l0的停止模式怎么唤醒_最强家庭娱乐系统+儿童模式,小度在家智能屏X8开售抢先体验...
  6. html5点击视频跳转,javascript – 播放后重定向html5视频
  7. java 操作系统 模拟 daima_编写一个程序,利用Java语言模拟操作系统进程调度管理...
  8. vba中split用法
  9. 如何有效阅读英文数据手册?
  10. 机器学习导论(一)绪论
  11. IDEA开发工具当前窗口导入多个项目
  12. java工作流引擎Jflow父子流程demo
  13. python源码文件的后缀名_Python 源代码程序编译后的文件扩展名为_________。_学小易找答案...
  14. 实验2《MySQL数据库原理与应用》
  15. 计算机游戏的最新技术,搭载十代酷睿i7处理器 这台ROG冰刃4新锐拥有媲美台式游戏电脑的性能...
  16. 最详细的A/B test 原理
  17. A Game of Thrones(11)
  18. 生活细语:送给每一个热爱生活的人
  19. 小程序名片,让你彻底告别伸手递名片的烦恼!
  20. 可编程控制、微机接口及微机应用综合实验台

热门文章

  1. GridView的常用操作(增删改查)
  2. 《编程匠艺》读书笔记之七
  3. ASP.NET2.0中控件的简单异步回调
  4. Hadoop组件启停命令和服务链接汇总
  5. Kibana_X-Pack管理Elasticsearch权限
  6. Eclipse导入Elasticsearch源码
  7. wake on lan
  8. Springboot应用中线程池配置教程(2021版)
  9. C盘文件内容及清理思路
  10. java自动化执行javascript,Js代码执行__实现自动化