转载自  永远爱大家的   程序员小灰

这一期我们来深入介绍之前遗留的两个问题:

  1. Java当中CAS的底层实现

  2. CAS的ABA问题和解决方法

首先看一看AtomicInteger当中常用的自增方法 incrementAndGet:

public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;    }}
private volatile int value;
public final int get() {return value;}

这段代码是一个无限循环,也就是CAS的自旋。循环体当中做了三件事:1.获取当前值。2.当前值+1,计算出目标值。3.进行CAS操作,如果成功则跳出循环,如果失败则重复上述步骤。

这里需要注意的重点是 get 方法,这个方法的作用是获取变量的当前值。

如何保证获得的当前值是内存中的最新值呢?很简单,用volatile关键字来保证。有关volatile关键字的知识,我们之前有介绍过,这里就不详细阐述了。

接下来看一看compareAndSet方法的实现,以及方法所依赖对象的来历:

compareAndSet方法的实现很简单,只有一行代码。这里涉及到两个重要的对象,一个是unsafe,一个是valueOffset。

什么是unsafe呢?Java语言不像C,C++那样可以直接访问底层操作系统,但是JVM为我们提供了一个后门,这个后门就是unsafe。unsafe为我们提供了硬件级别的原子操作。

至于valueOffset对象,是通过unsafe.objectFieldOffset方法得到,所代表的是AtomicInteger对象value成员变量在内存中的偏移量。我们可以简单地把valueOffset理解为value变量的内存地址。

我们在上一期说过,CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

而unsafe的compareAndSwapInt方法参数包括了这三个基本元素:valueOffset参数代表了V,expect参数代表了A,update参数代表了B。

正是unsafe的compareAndSwapInt方法保证了Compare和Swap操作之间的原子性操作。

什么是ABA呢?假设内存中有一个值为A的变量,存储在地址V当中。

此时有三个线程想使用CAS的方式更新这个变量值,每个线程的执行时间有略微的偏差。线程1和线程2已经获得当前值,线程3还未获得当前值。

接下来,线程1先一步执行成功,把当前值成功从A更新为B;同时线程2因为某种原因被阻塞住,没有做更新操作;线程3在线程1更新之后,获得了当前值B。

再之后,线程2仍然处于阻塞状态,线程3继续执行,成功把当前值从B更新成了A。

最后,线程2终于恢复了运行状态,由于阻塞之前已经获得了“当前值”A,并且经过compare检测,内存地址V中的实际值也是A,所以成功把变量值A更新成了B。

这个过程中,线程2获取到的变量值A是一个旧值,尽管和当前的实际值相同,但内存地址V中的变量已经经历了A->B->A的改变。

当我们举一个提款机的例子。假设有一个遵循CAS原理的提款机,小灰有100元存款,要用这个提款机来提款50元。

由于提款机硬件出了点小问题,小灰的提款操作被同时提交两次,开启了两个线程,两个线程都是获取当前值100元,要更新成50元。

理想情况下,应该一个线程更新成功,另一个线程更新失败,小灰的存款只被扣一次。

线程1首先执行成功,把余额从100改成50。线程2因为某种原因阻塞了。这时候,小灰的妈妈刚好给小灰汇款50元。

线程2仍然是阻塞状态,线程3执行成功,把余额从50改成100。

线程2恢复运行,由于阻塞之前已经获得了“当前值”100,并且经过compare检测,此时存款实际值也是100,所以成功把变量值100更新成了50。

这个举例改编自《java特种兵》当中的一段例子。原本线程2应当提交失败,小灰的正确余额应该保持为100元,结果由于ABA问题提交成功了。

什么意思呢?真正要做到严谨的CAS机制,我们在Compare阶段不仅要比较期望值A和地址V中的实际值,还要比较变量的版本号是否一致。

我们仍然以最初的例子来说明一下,假设地址V中存储着变量值A,当前版本号是01。线程1获得了当前值A和版本号01,想要更新为B,但是被阻塞了。

这时候,内存地址V中的变量发生了多次改变,版本号提升为03,但是变量值仍然是A。

随后线程1恢复运行,进行Compare操作。经过比较,线程1所获得的值和地址V的实际值都是A,但是版本号不相等,所以这一次更新失败。

在Java当中,AtomicStampedReference类就实现了用版本号做比较的CAS机制。

1. Java语言CAS底层如何实现?

利用unsafe提供了原子性操作方法。

2. 什么是ABA问题?怎么解决?

当一个值从A更新成B,又更新会A,普通CAS机制会误判通过检测。

利用版本号比较可以有效解决ABA问题。

—————END—————

什么是CAS机制?(进阶篇)相关推荐

  1. CAS原理 基础篇(漫画版)

    注:本文转自 程序员小灰 微信公众号~ CAS基础篇 示例程序: 启动两个线程,每个线程中让静态变量count循环累加100次. 最终输出的count结果是什么呢?一定会是200吗? 加了同步锁之后, ...

  2. 【面试篇】Java多线程并发-Java中的CAS机制算法

    Java中的CAS机制算法 a.CAS例子 再讲解CAS机制之前,先来看一道经典的并发执行1000次递增的问题: public class Test { public static int count ...

  3. 对CAS机制的理解(二)

    一.Java当中CAS的底层实现 首先看看AtomicInteger的源码,AtomicInteger中常用的自增方法 incrementAndGet: public final int increm ...

  4. Java面试题-进阶篇(2022.4最新汇总)

    Java面试题-进阶篇 1. 基础篇 1.1 基本数据类型和包装类 1.2 Double转Bigdecimal可能会出现哪些问题?怎么解决? 1.3 equals 与 == 的区别? 1.4 Java ...

  5. CompletableFuture进阶篇-外卖商家端API的异步化

    CompletableFuture进阶篇-外卖商家端API的异步化 背景 为何需要并行加载 并行加载的实现方式 同步模型 NIO异步模型 为什么会选择CompletableFuture? Comple ...

  6. Enterprise Library Step By Step系列(十二):异常处理应用程序块——进阶篇

    一.把异常信息Logging到数据库 在日志和监测应用程序块中,有朋友提意见说希望能够把异常信息Logging到数据库中,在这里介绍一下具体的实现方法. 1.创建相关的数据库环境: 我们可以用日志和监 ...

  7. Docker 数据卷之进阶篇

    Docker 数据卷之进阶篇 原文:Docker 数据卷之进阶篇 笔者在<Docker 基础 : 数据管理>一文中介绍了 docker 数据卷(volume) 的基本用法.随着使用的深入, ...

  8. Kafka核心设计与实践原理总结:进阶篇

    作者:未完成交响曲,资深Java工程师!目前在某一线互联网公司任职,架构师社区合伙人! kafka作为当前热门的分布式消息队列,具有高性能.持久化.多副本备份.横向扩展能力.我学习了<深入理解K ...

  9. SQL Server调优系列进阶篇(如何维护数据库索引)

    前言 上一篇我们研究了如何利用索引在数据库里面调优,简要的介绍了索引的原理,更重要的分析了如何选择索引以及索引的利弊项,有兴趣的可以点击查看. 本篇延续上一篇的内容,继续分析索引这块,侧重索引项的日常 ...

最新文章

  1. boot spring 打印请求参数_SpringBoot打印请求参数与响应参数
  2. 转载 - 背景透明,文字不透明【学习】
  3. 使用aSpotCat控制您的Android应用权限
  4. 直角三角形知道两边求角度_每日一讲:解直角三角形(3.21)
  5. select默认选中的option_macOS下妙用option按键
  6. springcloud云服务架构-HongHu commonservice-eureka项目构建过程
  7. vscode,git提交push,需要每次都输入账号密码username,password
  8. SQL---- 为树添加一个字段(是否有子节点)
  9. Centos7下Yum安装PHP5.5,5.6,7.0
  10. 2.Mysql数据库的优化技术(1)
  11. java eclipse参考文献_2019-2020年java毕设论文参考文献.doc
  12. 大学“电路分析基础”试题合集第八章
  13. 音视频转换器哪个好?嗨格式视频转换器来了
  14. con和com开头单词规律_con和com的前缀区别。背单词时总是搞不清什么时候是con什么时候是com之后找到了不同之处,c...
  15. Python 读写文件操作: with open() as f,pickle,pmml,数据库
  16. 常用网线T568A或T568B类型详细讲解
  17. 乱码原因产生和解决方案
  18. [博弈论] Nim游戏及SG函数(经典+台阶+集合+拆分)
  19. FASTAPI接口服务
  20. python更复杂的输出格式

热门文章

  1. [PAT乙级]1033 旧键盘打字(getline()读入)
  2. [Java基础]int和String的相互转换
  3. oracle实现序列,oracle中创建序列和自增长列的实现方式
  4. pip ImportError: cannot import name 'main' after update
  5. 【清华集训2014】Sum)(类欧几里得算法)
  6. CF1157G. Inverse of Rows and Columns
  7. [NOIP2016]愤怒的小鸟(状压DP)
  8. Codeforces Round #717 (Div. 2)
  9. AtCoder Beginner Contest 203(Sponsored by Panasonic)题解
  10. 洛谷P7518:宝石(倍增、可撤销并查集)