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,一经查实,立即删除!

相关文章

request.setCharacterEncoding(“utf-8“) 失效问题

指定后可以通过request.getParameter()获取自己想要的字符串,如果没有提前指定&#xff0c;则会按照服务器端默认的“iso-8859-1”来进行编码&#xff1b;该方法只对post请求有效&#xff0c;对get请求无效&#xff1b;对于get请求&#xff0c;应该在server.xml中指定&#xff…

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

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

python中使用什么来实现异常捕捉_Python异常原理及异常捕捉实现过程解析

关于选课程序&#xff0c;最近着实有点忙&#xff0c;没机会复习os、pickle两部分模块&#xff0c;所以数据储存和字典读取成为了一个问题&#xff0c;大致原理知道&#xff0c;但是具体操作可能还是得返回去再好好看看&#xff0c;所以目前就提前开始学习新的知识了&#xff0…

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

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

python读取文件内容操作_Python 3.6 读取并操作文件内容

下面为大家分享一篇Python 3.6 读取并操作文件内容的实例&#xff0c;具有很好的参考价值&#xff0c;希望对大家有所帮助。一起过来看看吧所使用python环境为最新的3.6版本Python中几种对文件的操作方法&#xff1a;将A文件复制到B文件中去(保持原来格式)读取文件中的内容,返回…

python scikit learn 关闭开源_慕课|Python调用scikit-learn实现机器学习(一)

一、机器学习介绍及其原理1.什么是人工智能&#xff1f;机器对人的思维信息过程的模拟&#xff0c;让它能相认一样思考。a.输入 b.处理 c.输出根据输入信息进行模型建构、权重更新&#xff0c;实现最终优化。特点&#xff1a;信息处理、自我学习&#xff0c;优化升级2.人工智能…

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

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

风变编程python论文_如何看待风变编程的 Python 网课?

作为风变推出的python课程的一名学员&#xff0c;很幸运能与风变相遇&#xff0c;更庆幸自己选择学习python&#xff0c;我学习的是python基础课程和爬虫精进课程&#xff0c;目前已经学完了python基础课程&#xff0c;不得不说对我的感触很深&#xff0c;非常想要继续学下去&a…

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

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

POI读取word文件,(支持HSSF和XSSF两种方式)

POI读取word文件&#xff0c;&#xff08;支持HSSF和XSSF两种方式&#xff09; 参考&#xff1a;HSSF&#xff0c;XSSF&#xff0c;SXSSF三种方式 1.引用maven&#xff08;版本必须一致&#xff09; <dependency><groupId>org.apache.poi</groupId><art…

pythonsqlite事务_python sqlite3 的事务控制

Python sqlite3 的事务控制官方文档的描述&#xff1a;Controlling TransactionsBy default, the sqlite3 module opens transactions implicitly before a Data Modification Language (DML) statement (i.e. INSERT/UPDATE/DELETE/REPLACE), and commits transactions implic…

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

二叉树遍历的超简单方法 参考链接&#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…

python 绘图 hist bin参数_Python-hist,distplot bin宽度不一致问题的解决方案

python的hist有一个bug&#xff0c;之前一直没有解决。绘制直方图的时候&#xff0c;往往出现两组数据由于分布不一样&#xff0c;&#xff0c;导致出来的图片中&#xff0c;虽然是一样数目的bins&#xff0c;但是bin的宽窄不同。而我想得到的是&#xff0c;&#xff0c;虽然数…

Nginx反向代理的使用

1. 常用服务器比较 apache: 功能完善&#xff0c;历史悠久&#xff0c;模块支持非常丰富&#xff0c;属于重量级产品&#xff0c;比较耗费内存。缺点:处理每一个php比较费资源&#xff0c;导致如果高并发时会耗费服务器资源无法处理更多请求。 lighttpd: 内存开销低&#xff0…

python继承语法_python中继承父类的例子(python3的语法)

#codingutf8class Cup:#构造函数&#xff0c;初始化属性值def __init__(self,capacity,color):self.capacitycapacityself.colorcolordef retain_water(self):print("杯子颜色&#xff1a;"self.color"&#xff0c;杯子容量&#xff1a;"self.capacity&quo…

Rabbit发送消息,消费者消费异常

Rabbit发送消息&#xff0c;消费者消费异常 背景&#xff1a; 在步骤1下创建订单&#xff0c;步骤2下提交消息 Transactionalpublic void tradeHandler(Map map) { // 1.生成统一订单 var unifyOrder orderService.create(orderService.getUnifyOrderDTO(fee, alipayOrder));…

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

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

如何使用explain进行SQL语句调优

参考&#xff1a;https://mp.weixin.qq.com/s/kYcrHtE82-sOqNOp_qM4Ig