简介

CAS的全称是compare and swap,它是java同步类的基础,java.util.concurrent中的同步类基本上都是使用CAS来实现其原子性的。

CAS的原理其实很简单,为了保证在多线程环境下我们的更新是符合预期的,或者说一个线程在更新某个对象的时候,没有其他的线程对该对象进行修改。在线程更新某个对象(或值)之前,先保存更新前的值,然后在实际更新的时候传入之前保存的值,进行比较,如果一致的话就进行更新,否则失败。

注意,CAS在java中是用native方法来实现的,利用了系统本身提供的原子性操作。

那么CAS在使用中会有什么问题呢?一般来说CAS如果设计的不够完美的话,可能会产生ABA问题,而ABA问题又可以分为两类,我们先看来看一类问题。

更多精彩内容且看:

更多内容请访问www.flydean.com

第一类问题

我们考虑下面一种ABA的情况:

  1. 在多线程的环境中,线程a从共享的地址X中读取到了对象A。
  2. 在线程a准备对地址X进行更新之前,线程b将地址X中的值修改为了B。
  3. 接着线程b将地址X中的值又修改回了A。
  4. 最新线程a对地址X执行CAS,发现X中存储的还是对象A,对象匹配,CAS成功。

上面的例子中CAS成功了,但是实际上这个CAS并不是原子操作,如果我们想要依赖CAS来实现原子操作的话可能就会出现隐藏的bug。

第一类问题的关键就在2和3两步。这两步我们可以看到线程b直接替换了内存地址X中的内容。

在拥有自动GC环境的编程语言,比如说java中,2,3的情况是不可能出现的,因为在java中,只要两个对象的地址一致,就表示这两个对象是相等的。

2,3两步可能出现的情况就在像C++这种,不存在自动GC环境的编程语言中。因为可以自己控制对象的生命周期,如果我们从一个list中删除掉了一个对象,然后又重新分配了一个对象,并将其add back到list中去,那么根据 MRU memory allocation算法,这个新的对象很有可能和之前删除对象的内存地址是一样的。这样就会导致ABA的问题。

第二类问题

如果我们在拥有自动GC的编程语言中,那么是否仍然存在CAS问题呢?

考虑下面的情况,有一个链表里面的数据是A->B->C,我们希望执行一个CAS操作,将A替换成D,生成链表D->B->C。考虑下面的步骤:

  1. 线程a读取链表头部节点A。
  2. 线程b将链表中的B节点删掉,链表变成了A->C
  3. 线程a执行CAS操作,将A替换从D。

最后我们的到的链表是D->C,而不是D->B->C。

问题出在哪呢?CAS比较的节点A和最新的头部节点是不是同一个节点,它并没有关心节点A在步骤1和3之间是否内容发生变化。

我们举个例子:

public void useABAReference(){
CustUser a= new CustUser();
CustUser b= new CustUser();
CustUser c= new CustUser();
AtomicReference<CustUser> atomicReference= new AtomicReference<>(a);
log.info("{}",atomicReference.compareAndSet(a,b));
log.info("{}",atomicReference.compareAndSet(b,a));
a.setName("change for new name");
log.info("{}",atomicReference.compareAndSet(a,c));
}

上面的例子中,我们使用了AtomicReference的CAS方法来判断对象是否发生变化。在CAS b和a之后,我们将a的name进行了修改,我们看下最后的输出结果:

[main] INFO com.flydean.aba.ABAUsage - true
[main] INFO com.flydean.aba.ABAUsage - true
[main] INFO com.flydean.aba.ABAUsage - true

三个CAS的结果都是true。说明CAS确实比较的两者是否为统一对象,对其中内容的变化并不关心。

第二类问题可能会导致某些集合类的操作并不是原子性的,因为你并不能保证在CAS的过程中,有没有其他的节点发送变化。

第一类问题的解决

第一类问题在存在自动GC的编程语言中是不存在的,我们主要看下怎么在C++之类的语言中解决这个问题。

根据官方的说法,第一类问题大概有四种解法:

  1. 使用中间节点 - 使用一些不代表任何数据的中间节点来表示某些节点是标记被删除的。
  2. 使用自动GC。
  3. 使用hazard pointers - hazard pointers 保存了当前线程正在访问的节点的地址,在这些hazard pointers中的节点不能够被修改和删除。
  4. 使用read-copy update (RCU) - 在每次更新的之前,都做一份拷贝,每次更新的是拷贝出来的新结构。

第二类问题的解决

第二类问题其实算是整体集合对象的CAS问题了。一个简单的解决办法就是每次做CAS更新的时候再添加一个版本号。如果版本号不是预期的版本,就说明有其他的线程更新了集合中的某些节点,这次CAS是失败的。

我们举个AtomicStampedReference的例子:

public void useABAStampReference(){
Object a= new Object();
Object b= new Object();
Object c= new Object();
AtomicStampedReference<Object> atomicStampedReference= new AtomicStampedReference(a,0);
log.info("{}",atomicStampedReference.compareAndSet(a,b,0,1));
log.info("{}",atomicStampedReference.compareAndSet(b,a,1,2));
log.info("{}",atomicStampedReference.compareAndSet(a,c,0,1));
}

AtomicStampedReference的compareAndSet方法,多出了两个参数,分别是expectedStamp和newStamp,两个参数都是int型的,需要我们手动传入。

总结

ABA问题其实是由两类问题组成的,需要我们分开来对待和解决。

本文的例子https://github.com/ddean2009/

learn-java-base-9-to-20

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/aba-cas-stamp/

本文来源:flydean的博客

欢迎关注我的公众号:程序那些事,更多精彩等着您!

ABA问题的本质及其解决办法的更多相关文章

  1. Java中读取txt文件中中文字符时,出现乱码的解决办法

    这是我写的一个Java课程作业时,遇到的问题. 问题描述: 我要实现的就是将txt文件中的内容按一定格式读取出来后,存放在相应的数组. 我刚开始运行时发现,英文可以实现,但是中文字符就是各种乱码. 最 ...

  2. Android内存泄漏的本质原因、解决办法、操作实例

    今年最后一个迭代终于结束了,把过程中碰到的不熟悉的东西拉出来学习总结一下   内存泄漏的本质是:[一个(巨大的)短生命周期对象的引用被一个长生命周期(异步生命周期)的对象持有]   这个东西分为两个部 ...

  3. 记一次由于Java泛型类型擦除而导致的问题,及解决办法

    中所周知,Java中的泛型并不像C++.C#一样是真正的泛型,其泛型是通过类型擦除来实现的.具体什么是类型擦除,可以参看这篇博文:http://icyfenix.iteye.com/blog/1021 ...

  4. Windows UDP socket recvfrom返回10054错误的解决办法

    现象: 在Windows 7系统上,A使用UDP socket,调用sendto函数向一个目标地址B发送数据,但是目标地址B没有接收数据,如果A此时立即调用recvfrom试图接收目标地址B发回的数据 ...

  5. Android性能优化之Systrace工具介绍(一) _&& Systrace生成的trace.html打开空白或者打不开的解决办法

    1.必须用Chrome打开 2.在mac电脑上,可能Chrome打开也是空白,解决办法是:在chrome地址栏中输入”chrome:tracing”,然后点击load按钮load你的trace.htm ...

  6. .Net内存泄露原因及解决办法

    .Net内存泄露原因及解决办法 1.    什么是.Net内存泄露 (1).NET 应用程序中的内存 您大概已经知道,.NET 应用程序中要使用多种类型的内存,包括:堆栈.非托管堆和托管堆.这里我们需 ...

  7. mysql保存中文乱码的原因和解决办法

    当你遇到这个mysql保存中文乱码问题的时候,期待找到mysql保存中文乱码的原因和解决办法这样一篇能解决问题的文章是多么激动人心.    也许30%的程序员会选择自己百度,结果发现网友已经贴了很多类 ...

  8. mht文件无法打开的解决办法

    对于喜欢上网的人士来说,经常会将自己看到的好的文章保存下来,以便日后再次翻阅,保存方法有两种:一种是通过浏览器的收藏夹进行收藏,这种方式适合于能够一直上网的电脑:另一种是通过浏览器“文件->另存 ...

  9. DRC错误解决办法

    一.WARNING(ORCAP-1589): Net has two or more aliases - possible short? 错误原因:一个网络有两个网络标号,可能造成短路! 问题本质:原 ...

  10. SpringCloud+Feign环境下文件上传与form-data同时存在的解决办法(2)

    书接上文. 上文中描述了如何在 SpringCloud+Feign环境下上传文件与form-data同时存在的解决办法,实践证明基本可行,但却会引入其他问题. 主要导致的后果是: 1. 无法与普通Fe ...

随机推荐

  1. Mqtt开发笔记:Mqtt服务器搭建

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  2. python中动态生成类type的用法

    示例:正常创建类 class Person(object): def __init__(self): self.name = name self.age = age p = Person(" ...

  3. 大众点评-CAT监控平台

    前言 我们禀着发现问题,解决问题的方针,针对后台诸多的服务,如何实时监控接口性能和访问频率,还要统计大盘信息?CAT作为大众点评开源的系统监控平台项目,下面就介绍一下CAT平台的搭建步骤. CAT作为 ...

  4. javascript浮点数相减、相乘出现一长串小数

    149.7 * 100 = 14969.999999999998 3.57 - 2.33 = 1.2399999999999998 这是JavaScript浮点运算采用IEEE 754标准导致的Bug ...

  5. 【Azure Developer】Python代码获取的Token出现'Authentication_MissingOrMalformed'问题

    问题描述 Python 调用Azure AD中所注册的应用生成Token代码: import requests, json client_id = 'yourclientid' client_secr ...

  6. Big-Yellow的算法工程师进阶之路

    Big-Yellow的算法工程师进阶之路 一.基础算法 二.基础数据结构 2.1 链表[1] 2.1.1 基础理论 链表是一种以链的形式来存储数据的数据结构.链表的结构:每一个数据都与其后一个数据相连 ...

  7. Tomcat8.5简介

    1. Tomcat简介[1] Apache Tomcat是Servlet/JSP的容器.Tomcat8.5 实现了由 JCP 组织 (Java Community Process) 制定的Servle ...

  8. Java 异常处理(2) : 异常处理的方式二:throws + 异常类型

    1 package com.bytezero.throwable; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 impor ...

  9. 宝塔Linux面板 https://www.bt.cn/ 服务器环境搭建软件

    宝塔Linux面板 https://www.bt.cn/

  10. Typora自定义主题详解--打造自己的专属样式

    你真的会使用Typora吗? 欢迎关注博主公众号「Java大师」, 专注于分享Java领域干货文章, 关注回复「主题」, 获取大师使用的typora主题: http://www.javaman.cn/ ...