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

通过对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的问题。

867a391e525c748b748bb5db9f2b6d69.png

线程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 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:

1763228be536749e3c4b9ffe1f73667b.png

所以每种技术都有它的两面性,在解决了一些问题的同时也出现了一些新的问题,在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 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(); }}

运行结果如下:

462448291aea232ef0a479c9665fd53a.png

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

1:成员变量

private volatile Pair pair;

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

private static class Pair {//封装了我们传递的对象 final T reference;//这个就是boolean标记 final boolean mark; private Pair(T reference, boolean mark) { this.reference = reference; this.mark = mark; } static  Pair of(T reference, boolean mark) { return new Pair(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 current = pair; return expectedReference == 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的问题的。一个伪流程图如下:

b5f4ffb041692f081c921dec588257f4.png

二、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 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(); }}

运行结果:

0420446096f8b274222fbaefcb40f954.png

1:成员变量

private volatile Pair pair;private static class Pair { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static  Pair of(T reference, int stamp) { return new Pair(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 current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp)));}
fd817f32ae855aa5fea7600540eea18f.png

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558336.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 观察者模式_重学 Java 设计模式:实战观察者模式「模拟类似小客车指标摇号过程,监听消息通知用户中签场景」...

一、前言知道的越多不知道的就越多编程开发这条路上的知识是无穷无尽的&#xff0c;就像以前你敢说精通Java&#xff0c;到后来学到越来越多只想写了解Java&#xff0c;过了几年现在可能想说懂一点点Java。当视野和格局的扩大&#xff0c;会让我们越来越发现原来的看法是多么浅…

图纸管理软件_造价20万以内的农村别墅长啥样?挑选5套图纸,让城里人羡慕吧...

在老家盖房算是一件“光宗耀祖”的事情&#xff0c;现在的物价高&#xff0c;想盖一栋房随便都要几十万&#xff0c;对于一般家庭来说&#xff0c;要拿出这笔数字并不是一件容易的事&#xff0c;通常大家的预算都有限&#xff0c;希望能花最少的钱盖最好的房&#xff0c;这种心…

python+ BeautifulSoup抓取“全国行政区划信息查询平台”的省市区信息

全国行政区划信息查询平台地址&#xff1a;http://xzqh.mca.gov.cn/map 检查网页源码&#xff1a; 检查网页源码可以发现&#xff1a; 所有省级信息全部在javaScript下的json中&#xff0c;会在页面加载时加载json数据&#xff0c;填充到页面的option中。 1.第一步&#xff1…

32利用文件系统保存数据_网易技术实践|Docker文件系统实战

在本文中&#xff0c;我们来实战构建一个Docker镜像&#xff0c;然后实例化容器&#xff0c;在Docker的生命周期中详细分析一下Docker的文件存储情况和DockerFile优化策略。在开始实战之前&#xff0c;我们先介绍一个概念&#xff0c;联合文件系统&#xff08;Union File Syste…

二叉树遍历的超简单方法(详细、简单)

二叉树遍历的超简单方法 参考链接&#xff1a;https://wenku.baidu.com/view/e5463e4203d8ce2f0166230a.html 已修改部分问题。 三种常见二叉树的遍历&#xff1a; 先序遍历的递归算法定义&#xff08;简称根左右&#xff09; 若二叉树非空&#xff0c;则依次执行如下操作&a…

怎么看我装的sql能不能用_深入浅出sql优化(三)之单表索引优化

大家好&#xff0c;我是闲水&#xff0c;每天更新java最新最热技术&#xff0c;对java感兴趣的朋友记得关注一下哦。注意 &#xff1a;这是SQL性能优化第三章&#xff0c;点击关注查看前置内容。上篇文章我们主要了解了索引优化的标尺"Explain"怎么用&#xff0c;这一…

java类加载顺序(spring容器下)

执行顺序&#xff1a;父类静态块–>子类静态块–>父类非静态块–>父类构造方法–>子类非静态块–>子类构造方法–>自动装载的方法 子类和父类均加上Service注解&#xff0c;将其交给spring容器管理。 父类&#xff1a; Service public class Father {publ…

jwt token 太长_理解 JWT 鉴权的应用场景及使用建议

JWT 介绍JSON Web Token(JWT)是一个开放式标准(RFC 7519)&#xff0c;它定义了一种紧凑(Compact)且自包含(Self-contained)的方式&#xff0c;用于在各方之间以JSON对象安全传输信息。这些信息可以通过数字签名进行验证和信任。可以使用秘密(使用HMAC算法)或使用RSA的公钥/私钥…

microsoft vbscript编译器错误怎么解决_win7系统ie应用程序错误怎么办 ie应用程序错误解决方法【详解】...

windows系统自带的ie浏览器很少用户会去使用到&#xff0c;它运行起来比其他的浏览器要慢很多&#xff0c;而且经常会出现各种各样的问题&#xff0c;最近有位win7系统用户&#xff0c;在使用ie浏览器的时候&#xff0c;发生了应用程序错误的情况&#xff0c;那么win7系统ie应用…

修改固态硬盘的物理序列号_买固态怕踩坑?收下这些软件,轻松鉴别好坏

再有半个月&#xff0c;就迎接年中的促销旺季。不少朋友都希望趁着各种优惠、跳楼价&#xff0c;组一台梦想机、升级一下自己的电脑。说到体验升级&#xff0c;相信就算是DIY新手和硬件小白都知道&#xff0c;要想电脑快&#xff0c;必须选固态。但是固态参数多&#xff0c;又是…

《常用控制电路》学习笔记——数控锁相环调速电路

序言 今天开始我将把自己学习《常用控制电路》的一些内容发到互联网上&#xff0c;希望能和大家交流学习。 这本书主要介绍了一系列控制电路的经典案例&#xff0c;进行了电路和代码的设计&#xff0c;我将把这本书中学习到的内容和学习的过程尽量准确的分享出来与大家交流&…

《常用控制电路》学习笔记——数字控制直流电动机调速电路

书中该节讲述的电路通过ADC芯片将电位器的阻值转换为电压量&#xff0c;然后使用单片机输出PWM进行电路控制。本电路也可扩展应用至需要模拟量输入与输出的场合。 目录 一、系统设计思路 二、各模块电路分析 2.1电路构成 2.2工作状态分析 三、程序分析 3.1 变量定义 3.…

《常用控制电路》学习笔记——数控直流恒流源电路

书中该项目内容设计了一个从交流220V市电作为供电电源的恒流源电路&#xff0c;并且此电路可由加减计数器74LS193控制输出的电流大小。 目录 一、系统方案 二、各模块电路分析 整流滤波及稳压电路 数字量控制电路 数模转换电路 数控恒流源产生电路 三、电路仿真结果 四…

图像太宽无法输出请裁剪图像或降低分辨率然后重试_真·无监督!延世大学提出图像到图像无监督模型,实验结果超SOTA...

作者 | 蒋宝尚编辑 | 丛 末图像翻译目的是用模型将源域图像转换到目标域图像&#xff0c;通常涉及标签图到场景图的转换、图像风格、人脸的属性变换、标签图到场景图的转换。图像翻译任务自生成对抗网络提出就得到了快速发展&#xff0c;例如经典的pix2pix、CycleGAN、StarGAN。…

php serialize和json_encode哪个更快_世界 10 大编程语言,Java 不是第一,PHP 才第五...

来源&#xff1a;toutiao.com/a6764554659349676557/如果你是软件开发领域的新手&#xff0c;那么你会想到的第一个问题是“如何开始&#xff1f;”编程语言有数百种可供选择&#xff0c;但是你怎么发现哪个最适合你&#xff0c;你的兴趣和职业目标又在哪里呢&#xff1f;选择最…

tomcat7.0支持什么版本的jdk_恭喜你喜提JDK,那你知道JDK是什么吗?先来看看吧

点击蓝字关注一行JDK 大家都知道电脑的操作系统是由汇编和C语言写出&#xff0c;因此操作系统无法直接识别其他语言。这时我们就需要为我们写的Java程序配备一名翻译官 ----- 编译环境&#xff0c;将Java程序翻译成电脑可以识别的程序&#xff0c;C或者汇编。 那么对于这个…

我的python 入门 安装 -- hello world

我的python 安装–>“hello world” 最近老听到关于python的声音&#xff0c;而且越来越强烈。就好奇下载了下&#xff0c;在win10 的应用商店下载的 够傻瓜了吧 环境变量也不用配置 直接上手 hello world了 cmd 窗口 输入 python -v 正常显示 显示版本号 不能正常显示…

记录spring、springboot集成apollo配置中心

一, spring集成apollo&#xff0c;前提是apollo配置中心服务端已经在运行中 上面是我在阿里云服务搭建的apollo配置中心服务端&#xff0c;登录后的样子。没有搭建服务端的小伙伴&#xff0c;请先搭建好apollo的服务端 然后点击‘创建项目’&#xff0c;新建测试用的项目 填…

基本农田卫星地图查询_发现谷歌地图替代网站,卫星地图街景功能都能用

众所周知&#xff0c;由于谷歌地图&#xff08;Google Maps&#xff09;在国内不能访问&#xff0c;很多人就没有办法通过谷歌地图来获得服务。谷歌地图是目前全球最受欢迎的世界地图网站&#xff0c;在2005年以前&#xff0c;谷歌地图就收录了美国、英国、加拿大三个国家的地图…

记录 Linux crontab 的使用

记录一次简单的Linux定时任务----》每周定时备份数据库结构及数据 环境&#xff1a;阿里云服务器 vim 命令&#xff1a;输入i/a 进入输入模式&#xff0c;输入完成后&#xff0c;esc键&#xff0c;退出输入模式&#xff0c;确定无误后&#xff0c;输入“:wq”,保存退出 Linux 环…