当前位置: 首页 > news >正文

Java并发编程:CAS操作

1. 简介

  CAS 全称是 Compare-and-Swap,即比较加交换,假设我们要对内存中的某个地址进行 CAS 操作,该地址当前值是 V,我们给定预期值 A 和新值 B,如果 V == A,那么就将该地址的值更新为 B 并返回 true,否则什么也不做只返回 false,不过这些步骤必须要原子地进行;从 CAS 的执行过程我们可以看到,CAS 的本质就是原子地对某一个内存地址先进行一致性检查,如果一致性检查通过,则执行写入操作,返回成功,如果一致性检查不通过,则返回失败,通知调用方,该内存地址发生了冲突。CAS 是如何保证操作时原子的呢,这是由底层处理器的原子原语来保证的,因此能否执行 CAS 操作,需要 CPU 的支持,当然现代的处理器都时支持 CAS 操作的,处理器提供的 CMPXCHG 指令就是执行 CAS 操作的。Java 对 CAS 的支持是通过 sun.misc.Unsafe 类来支持的,Unsafe 内部提供了本地方法用于对内存中的某一个地址执行 CAS 操作。

2. CAS 的优势和局限性

CAS的优势

CAS 支持对某一个内存地址进行原子的检查 + 更新的复合操作,在很多场合下,可以代替系统互斥量和信号量,减少应用程序的系统调用,并且可以大大减少锁的使用,无锁的非阻塞同步算法就是基于 CAS 进行设计和实现的,这些非阻塞同步算法能够大大提升系统的并发性能和可伸缩性,并且能够很好地防止活跃性故障的发生。

CAS的局限性
  1. CAS 只能对一个内存地址的值进行单一的检查 + 改写的原子操作,因此使用 CAS 进行同步算法设计非常复杂,需要引入复杂的数据结构,这些复杂性会带来很多额外的开销,并且极易出错;
  2. CAS 操作很难将多个对像的多个调用组合成一次原子调用,随着需要同步的变量数增加,使用 CAS 来设计无锁同步算法的复杂度就会指数级上升,最终变得不可能。因此,CAS 适合应用于共享变量很少的场合。
  3. 使用 CAS 设计的同步算法可能带来 ABA 问题,为了解决 ABA 问题会引入额外的性能开销。注意并非 CAS 本身具有ABA问题,而是基于 CAS 设计的同步算法。
ABA问题

下面的代码是 CAS 的典型应用模式,在第 7 行,将原子变量 v 的值保存在本地的副本 oldVal 上面,用于后面做一致性检查,第 8 行计算新值,第 9 行执行 CAS 操作,如果在执行 CAS 操作的时候发现 v 的值与本地的副本 oldVal 不相等,那么说明检测到了冲突,有其他线程正在并发调用 calac 方法,那么就要进行失败重试,但是有一种情况,也发生了冲突,但是却能够通过一致性检查,比如当前线程执行完第7行(此时 v 的值是 A,oldVal 的值也是 A)但还未执行第9行时,有1个线程成功将 v 值从 A 改成了 B,而另外一个线程又成功将 v 的值从 B 改回 A,v 的值发生 A -> B -> A 的变化,这时当前线程执行到第9行,因为 v 的当前值等于其副本 oldVal,所以成功通过了一致性检查,成功更新了 v 的值,这时候就发生了 ABA 错误,ABA 错误导致冲突检查机制失效,当前线程并未检测到实际已经发生了的冲突。

 1 -> 	public class Demo {2 -> 	3 -> 		private final AtomicInteger v = new AtomicInteger();4 -> 	5 -> 		public void calac() {6 -> 			while (true) {7 -> 				int oldVal = v.get();8 -> 				int newVal = calacNewVal(oldVal);9 -> 				if(v.compareAndSet(oldVal, newVal)) {
10 -> 					break;
11 -> 				}
12 -> 			}
13 -> 		}
14 -> 	
15 -> 		int calacNewVal(int oldVal) {
16 -> 	    	...
17 -> 		}
18 -> 	}

ABA 问题并不是一定会发生,当原子变量的值不会重复时,比如变量的值是单调增加或单调递减的,这种情况下就不会出现 ABA 问题。一般我们使用原子变量是用来做同步计数器,因此它的值是递增的,所以当使用原子变量做计数器时无需担心 ABA 问题,但是对于原子引用类 AtomicReference,因为它的值(引用地址)是完全随机的,而且 AtomicReference 一般应用于各种无锁的链式数据结构中,因此需要考虑解决 ABA 问题,JDK 也提供了带有版本戳的原子引用类 AtomicStampedReference,能够避免 AtomicReference 的 ABA 问题,但同时也带来了额外的存储空间消耗和性能开销。

http://www.dtcms.com/a/193110.html

相关文章:

  • Ubuntu操作合集
  • 变分自编码器(Variational Autoencoder, VAE)
  • 博途软件直接寻址AMS348i读取位置值详解
  • 【C语言】19. ⾃定义类型:联合和枚举
  • 登录接口的密码进行RSA加密Java脚本
  • 牛客网NC218480统计正负数个数
  • VMware中快速安装与优化Ubuntu全攻略
  • 无人机避障——深蓝学院浙大Fast-planner学习部分(前端部分)
  • SpringBoot基础(静态资源导入)
  • 渗透测试流程-上篇
  • XBL6501/02/03在POE设备上的应用方案
  • 常用的关系性统计方法
  • 受控组件和非受控组件的适用场景分别是什么?
  • GRE作文总结和归纳
  • web第二次课后作业--设计一个注册登录系统
  • 涨薪技术|0到1学会性能测试第65课-SQL捕获阻塞事件
  • Vue3学习(组合式API——reactive()和ref()函数详解)
  • 实验5 DNS协议分析与测量
  • HttpServletRequest常用功能简介-笔记
  • SpringAOP
  • openEuler24.03 LTS下安装MySQL8.0.42
  • Linux 动态库热加载
  • 计量——异方差的检验及其修正
  • 报考叉车证需要参加哪些培训和考试?
  • 24小时不打烊的知识宝库——RFID智能书柜
  • SpringBoot 3.4.5版本导入Lomobok依赖后无法生效的问题
  • Vuex和Vue的区别
  • 计算机组成与体系结构:替换策略(MRU LRU PLRU LFU)
  • WebRTC技术下的EasyRTC音视频实时通话SDK,助力车载通信打造安全高效的智能出行体验
  • HDFS的概述