ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java面试题CAS原理

2022-08-05 14:32:57  阅读:242  来源: 互联网

标签:面试题 Java CAS 原子 版本号 线程 操作 内存地址


CAS

CAS(Compare And Swap,比较并交换)自旋抢锁。
微信交流群:Java技术沟通群⑤(点击加入)

原理

CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。
CAS 操作用得比较多的是 sun.misc 包的 Unsafe 类,UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
Java 并发包大量使用 Unsafe 类的 CAS 操作。AtomicXXX原子类(本质是自旋锁 + CAS)

使用CAS进行无锁编程

CAS是一种无锁算法,该算法关键依赖两个值——期望值(旧值) 和新值,底层CPU利用原子操作判断内存原值与期望值是否相等,如果 相等就给内存地址赋新值,否则不做任何操作。
使用CAS进行无锁编程的步骤大致如下:
(1)获得字段的期望值(oldValue)。
(2)计算出需要替换的新值(newValue)。
(3)通过CAS将新值(newValue)放在字段的内存地址上,如果 CAS失败就重复第(1)步到第(2)步,一直到CAS成功,这种重复俗 称CAS自旋。
在这里插入图片描述
在这里插入图片描述
接下来执行线程B的CAS(100,300)操作,此时内存地址的值为200, 不等于CAS的期望值100,线程B操作失败。线程B只能自旋,开始新的 循环,这一轮循环首先获取到内存地址的值200,然后进行 CAS(200,300)操作,这一次内存地址的值与CAS的预期值(oldValue) 相等,线程B操作成功。

当CAS将内存地址的值与预期值进行比较时,如果相等,就证明内存地址的值没有被修改,可以替换成新值,然后继续往下运行;如果不相等,就说明内存地址的值已经被修改,放弃替换操作,然后重新自 旋。当并发修改的线程少,冲突出现的机会少时,自旋的次数也会很 少,CAS的性能会很高;当并发修改的线程多,冲突出现的机会多时, 自旋的次数也会很多,CAS的性能会大大降低。所以,提升CAS无锁编 程效率的关键在于减少冲突的机会。

CAS操作的弊端和规避措施

CAS操作的弊端主要有以下三点:

1.ABA问题
存在 ABA 问题,即原来内存地址的值是 A,然后被改为了 B,再被改为 A 值,此时 CAS 操作时认为该值未被改动过,ABA 问题可以引入版本号来解决,每次改动都让版本号 +1。Java 中处理 ABA 的一个方案是 AtomicStampedReference 类,它是使用一个 int 类型的字段作为版本号,每次修改之前都先获取版本号和当前线程持有的版本号比对,如果一致才进行修改操作,并把版本号 +1。
2.只能保证一个共享变量之间的原子性操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,CAS就无法保证操作的原子性。
一个比较简单的规避方法为:把多个共享变量合并成一个共享变量来操作。

JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个AtomicReference实例后再进行CAS操作。比如有两 个共享变量i=1、j=2,可以将二者合并成一个对象,然后用CAS来操作该合并对象的AtomicReference引用。

3.开销问题
自旋CAS如果长时间不成功(不成功就一直循环执行,直到成功),就会给CPU带来非常大的执行开销。
解决CAS恶性空自旋的有效方式之一是以空间换时间,较为常见的方案为:

(1)分散操作热点,使用LongAdder替代基础原子类 AtomicLong,LongAdder将单个CAS热点(value值)分散到一个cells数 组中。

(2)使用队列削峰,将发生CAS争用的线程加入一个队列中排队,降低CAS争用的激烈程度。JUC中非常重要的基础类AQS(抽象队列同步器)就是这么做的。

CAS操作在JDK中的应用

CAS在java.util.concurrent.atomic包中的原子类、Java AQS以及显式锁、CurrentHashMap等重要并发容器类的实现都有非常广泛的应用。

在java.util.concurrent.atomic包的原子类(如AtomicXXX)中都使用 了CAS来保障对数字成员进行操作的原子性。

java.util.concurrent的大多数类(包括显式锁、并发容器)都是基于 AQS和AtomicXXX来实现的,其中AQS通过CAS保障它内部双向队列头部、尾部操作的原子性。

标签:面试题,Java,CAS,原子,版本号,线程,操作,内存地址
来源: https://www.cnblogs.com/henlan/p/16554176.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有