JUC并发工具-CAS机制

面试的时候经常被问到锁、JUC工具包等相关内容,其中CAS机制是必问题目,以下简单总结CAS的机制、CAS产生的ABA现象、CAS产生的ABA现象解决思路

1.什么是CAS?

CAS(Compare and Swap)是一种多线程同步的原子操作,用于解决并发环境下的数据竞争和线程安全问题。像我们平时使用到的JUC并发包下的AtomicInteger、AtomicLong、AtomicLong、AtomicBoolean等等底层都是基于CAS实现的,另外ReentrantLock、ConcurrentHashMap这些底层也是采用CAS机制实现。

2.CAS的原理?

CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B,首先比较某个内存位置的值与预期值是否相等,如果相等,则将新值写入该内存位置;如果不相等,则表示其他线程已经修改了该内存位置的值,操作失败。这样子就能保证原子性。

如AtomicInteger 类中compareAndSet方法如下,expectedValue是指内存中期望的值,newValue是指新值,VALUE是在类中的偏移量,用于后面CAS操作时使用

public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

: jdk17和jdk8实现有很大的不同

其属性如下

// 获取Unsafe的实例
private static final Unsafe U = Unsafe.getUnsafe();
// 标识value字段的偏移量
private static final long VALUE= U.objectFieldOffset(AtomicInteger.class, "value");
// 存储int类型值的地方,使用volatile修饰
private volatile int value;static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}

这里使用volatile的作用是为了保证可见性( 内存屏障),即一个线程的修改另一个线程可见,线程修改数据后往主存更新数据,另一个线程也从主存读取数据,从而保证可见性。

compareAndSet()方法底层调用Unsafe类的compareAndSwapInt()方法实现,这个方法有四个参数:

  • this:当前对象;
  • VALUE:对象中字段的偏移量;
  • expectedValue:内存中的旧值;
  • newValue:新的期望值;
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

Unsafe类的compareAndSwapInt()方法是一个本地方法,底层是使用C/C++写的,主要是调用CPU的CAS指令来实现。

再来看一个AtomicInteger 类中的核心方法,getAndIncrement()

public final int getAndIncrement() {return U.getAndAddInt(this, VALUE, 1);
}

getAndIncrement()方法底层是调用的Unsafe的getAndAddInt()方法,这个方法有三个参数分别表示,当前操作对象、对象中字段的偏移量、要增加的值

image-20231118112750087

getAndAddInt()方法底层会调用Unsafe类的compareAndSwapInt()方法

 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

3.ABA现象产生分析

为了方便演示,采用AtomicReference实现一个CAS机制导致的ABA现象,如下

public class CASCostABADemo {private static AtomicReference<Integer> sharedVariable = new AtomicReference<>(10);public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {int oldValue = sharedVariable.get();System.out.println("Thread 1 - Old value: " + oldValue);sleep(2000); // 线程1暂停1秒,给线程2足够的时间执行// 尝试修改共享变量的值boolean success = sharedVariable.compareAndSet(oldValue, 20);System.out.println("Thread 1 - Value changed: " + success);});Thread thread2 = new Thread(() -> {sleep(500); // 线程2暂停0.5秒// 修改共享变量的值为30,然后再修改回10sharedVariable.set(30);System.out.println("Thread 2 - Value changed to 30");sleep(500); // 线程2暂停0.5秒sharedVariable.set(10);System.out.println("Thread 2 - Value changed to 10");});thread1.start();thread2.start();Thread.sleep(3000);System.out.println("Final value: " + sharedVariable.get());}private static void sleep(long milliseconds) {try {Thread.sleep(milliseconds);} catch (InterruptedException e) {e.printStackTrace();}}
}

运行结果如下:

image-20231118114908295

具体的说

  • 线程1旧值是10,线程2旧值也是10,线程1休眠1秒,线程2得到CPU时间片,进入执行状态;
  • 线程2采用CAS机制,内存旧值是10,新值是30,发现不一致,则把10改成30,此时内存值是30;
  • 线程2继续执行把30改成10;
  • 线程1此时开始执行,发现内存值是10,旧的内存值也是10,因此把10改成20;

以上就是CAS可能会产生的ABA问题。

4.ABA问题解决

为了解决CAS的ABA现象,引入了AtomicStampedReference。我的个人理解CAS的 ABA现象,不能看成是设计的缺陷,可以理解为不同业务场景的选型问题,如果我要实现一个类似于倒计时的功能使用AtomicInteger 就能实现这样的需求。

public class CASABASolveDemo {private static AtomicStampedReference<Integer> sharedVariable = new AtomicStampedReference<>(10, 0);public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {//当前共享变量的值int oldValue = sharedVariable.getReference();//当前共享变量的版本号int oldStamp = sharedVariable.getStamp();System.out.println("Thread 1 - Old value: " + oldValue + ", Stamp: " + oldStamp);sleep(1000);// 尝试修改共享变量的值int newStamp = oldStamp + 1;boolean success = sharedVariable.compareAndSet(oldValue, 20, oldStamp, newStamp);System.out.println("Thread 1 - Value changed: " + success + ", New Stamp: " + newStamp);});Thread thread2 = new Thread(() -> {sleep(500);int[] stampHolder = new int[1];//获取当前共享变量的值,并且把版本号存储在stampHolder 数组中。int currentValue = sharedVariable.get(stampHolder);int stamp = stampHolder[0];System.out.println("Thread 2 - Old value: " + currentValue + ", Stamp: " + stamp);// 修改共享变量的值为30int newStamp = stamp + 1;sharedVariable.compareAndSet(currentValue, 30, stamp, newStamp);System.out.println("Thread 2 - Value changed to 30, New Stamp: " + newStamp);sleep(500);// 修改共享变量的值回10boolean success = sharedVariable.compareAndSet(30, 10, newStamp, newStamp + 1);System.out.println("Thread 2 - Value changed to 10: " + success + ", New Stamp: " + (newStamp + 1));});thread1.start();thread2.start();Thread.sleep(3000);System.out.println("Final value: " + sharedVariable.getReference() + ", Stamp: " + sharedVariable.getStamp());}private static void sleep(long milliseconds) {try {Thread.sleep(milliseconds);} catch (InterruptedException e) {e.printStackTrace();}}
}

image-20231118115951339

ry {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}


[外链图片转存中...(img-eJfEZdNk-1700280126493)]运行结果如上,最终的结果是10,是由线程2计算得到的最终结果,线程1操作失败。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/190203.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Nacos】配置管理、微服务配置拉取、实现配置热更新、多环境配置

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 Nacos 一、nacos实现配置管理1.1 统一配置管…

生成式AI模型量化简明教程

在不断发展的人工智能领域&#xff0c;生成式AI无疑已成为创新的基石。 这些先进的模型&#xff0c;无论是用于创作艺术、生成文本还是增强医学成像&#xff0c;都以产生非常逼真和创造性的输出而闻名。 然而&#xff0c;生成式AI的力量是有代价的—模型大小和计算要求。 随着生…

基础模型的自然语言处理能力综述

NLP作为一个领域为基础模型开辟了道路。虽然这些模型在标准基准测试中占据主导地位&#xff0c;但这些模型目前获得的能力与那些将语言描述为人类交流和思维的复杂系统的能力之间存在明显的差距。针对这一点&#xff0c;我们强调语言变异的全部范围&#xff08;例如&#xff0c…

H5ke11--1登录界面一直保存--用本地localStorage存储

目录 代码详解 localStage优点 :一直保存着 注意事项: storage属性们 代码详解 ke8学校陈老师H5-CSDN博客文章浏览阅读76次。实现H5中新增的三个元素&#xff1a;forEach的使用方法。https://blog.csdn.net/m0_72735063/article/details/134019012即此之后 当然可以分为按…

记一次用jlink调试正常,不进入调试就不能运行的情况

一、概述 我开机会闪烁所有指示灯&#xff0c;但是重新上电时&#xff0c;指示灯并没有闪烁&#xff0c;就像"卡死"了一样。 使用jlink的swd接口进行调试&#xff0c;需要多点几次运行才能跳转到main函数里面。 调试模式第一次点击运行&#xff0c;暂停查看函数堆栈…

Flask学习一:概述

搭建项目 安装框架 pip install Flask第一个程序 from flask import Flaskapp Flask(__name__)app.route(/) def hello_world():return "Hello World"if __name__ __main__:app.run()怎么说呢&#xff0c;感觉还不错的样子。 调试模式 if __name__ __main__:a…

Vue.js2+Cesium1.103.0 十四、绘制视锥,并可实时调整视锥姿态

Vue.js2Cesium1.103.0 十四、绘制视锥&#xff0c;并可实时调整视锥姿态 Demo <template><divid"cesium-container"style"width: 100%; height: 100%;"><divclass"control"style"position: absolute;right: 50px;top: 50px…

第十九章 绘图

目录 Java绘图类 Graphics 类 Graphics2D类 绘制图形 绘图颜色与画笔属性 设置颜色 设置画笔 绘制文本 设置字体 显示文字 显示图片 图像处理 放大与缩小 图像翻转 图像旋转 图像倾斜 Java绘图类 Graphics 类 Grapics 类是所有图形上…

MidJourney笔记(2)-面板使用

MidJourney界面介绍 接着上面的疑问。U1、U2、U3、U4、V1、V2、V3、V4分别代表着什么? U1、U2、U3、U4: U按钮是用于放大图片,数字即表示对应的图片,可以立即生成1024X1024像素大小的图片。这样大家在使用的时候,也方便单独下载。 其中数字顺序如下:

2023-11-18 Android Linux资源限制命令 ulimit,比如ulimit -d 是设置进程占用的最大数据段大小,默认是unlimited。

一、通过ulimit -a 命令可以查看当前的各种资源限制&#xff0c;比如ulimit -d 是 进程占用的最大数据段大小。 # ulimit -a -t: time(cpu-seconds) unlimited -f: file(blocks) unlimited -c: coredump(blocks) 0 -d: data(KiB) unlimited -s:…

屏蔽bing搜索框的今日热点

中国版的Bing简直比百度还恶心了&#xff0c;“今日热点”要是在搜索设置里关闭了就没法提供搜索建议了&#xff0c;不关吧看着又烦人&#xff0c;就像下图这样。另外还有右上角的下载bing app和Rewards图标也闲着没啥用&#xff0c;Rewards图标还老有小红点&#xff0c;真受不…

【giszz笔记】产品设计标准流程【5】

&#xff08;续上回&#xff09; 目录 五、原型设计 1.写在前面的话 2.原型是什么 3.画原型的工具 4.产品经理的复合能力 5.关于原型图 PS&#xff1a;这个系列&#xff0c;主要讨论的是产品设计的一般标准流程。这个流程也许每天都发生在我们的身边&#xff0c;我们也常…