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,然后再修改回10
            sharedVariable.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);

            // 修改共享变量的值为30
            int newStamp = stamp + 1;
            sharedVariable.compareAndSet(currentValue, 30, stamp, newStamp);
            System.out.println("Thread 2 - Value changed to 30, New Stamp: " + newStamp);

            sleep(500);

            // 修改共享变量的值回10
            boolean 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操作失败。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

融合语言模型中的拓扑上下文和逻辑规则实现知识图谱补全11.18

融合语言模型中的拓扑上下文和逻辑规则实现知识图谱补全 摘要1 引言2 相关工作2.1 事实嵌入法2.2 拓扑嵌入方法2.3 规则融合方法2.4 基于LM的方法 3 准备3.1 知识图谱和拓扑上下文3.2 KG中的逻辑规则4.3 三元组嵌入 5 实验和结果5.1 数据集和评价指标 摘要 知识图补全&#xf…

社区无人零售:投资新热点,创业新机遇

社区无人零售是一个备受关注的创业项目&#xff0c;被视为投资的“爆点”。与其他国家相比&#xff0c;无人零售在国内市场远未达到饱和&#xff0c;因此成为了当下的新风口。今天&#xff0c;我将详细分析这个创业项目&#xff0c;以帮助感兴趣的朋友们了解更多。 首先&#x…

【论文阅读笔记】Supervised Contrastive Learning

【论文阅读笔记】Supervised Contrastive Learning 摘要 自监督批次对比方法扩展到完全监督的环境中&#xff0c;以有效利用标签信息提出两种监督对比损失的可能版本 介绍 交叉熵损失函数的不足之处&#xff0c;对噪声标签的不鲁棒性和可能导致交叉的边际&#xff0c;降低了…

【Java SE】继承

学习完了类之后&#xff0c;我们将继续学习一个Java中的重点内容“继承” 继承 1.1 为什么需要继承 举例&#xff1a; 在Cat类中和Dog类中我们发现有很多一样的地方&#xff0c;这样写太浪费空间和内存了 我们可以把它相同的地方都用一个类来表示&#xff0c;并且使用它1.2 继…

【python零基础入门学习】python进阶篇之数据库连接-PyMysql-全都是干货-一起来学习吧!!!

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

Springboot+vue的学生成绩管理系统(有报告),Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的学生成绩管理系统&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目。 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家…

Windows11 python3.12 安装pyqt6 pyqt6-tools

Windows11 python3.12 安装pyqt6比较容易&#xff0c;但pyqt6-tools一直安装不上去。出错信息如下&#xff1a; (venv) PS D:\python_project\pyqt6> pip install pyqt6-tools Collecting pyqt6-toolsUsing cached pyqt6_tools-6.4.2.3.3-py3-none-any.whl (29 kB) Collec…

《少儿编程启蒙指南》

《少儿编程启蒙指南》大纲 本文详细阐述少儿编程启蒙&#xff0c;如果有人喜欢&#xff0c;往后我会继续更新迭代此文。 “Everyone should know how to program a computer, because it teaches you how to think.”—Steve Jobs 每个人都应该知道如何编程&#xff0c;因为它…

杭州信息安全

更轻量级的用户开销 (Lower online burden) 更灵活的通信模型 (Flexible metadata-private messaging) 一对一通信 >多对一、一对多通信 Group messaging Broadcast / anycast 元数据隐私保护技术在其他系统的推广

RocketMQ(二):原生API快速入门

RocketMQ系列文章 RocketMQ(一)&#xff1a;基本概念和环境搭建 RocketMQ(二)&#xff1a;原生API快速入门 目录 一、RocketMQ快速入门1、生产者发送消息2、消费者接受消息3、代理者位点和消费者位点 二、消费模型特点1、同一个消费组的不同消费者&#xff0c;订阅主题必须相…

Python每日一练@前言

Python每日一练前言 导读 人生苦短&#xff0c;我用Python 大家好&#xff0c;我是鹅不糊涂 欢迎大家来到Python每日一练 好处 加强编程能力: 每日一练可以帮助提升编程技能&#xff0c;通过解决各种编程问题和挑战&#xff0c;你能够不断锻炼自己的逻辑思维和解决问题的能力…

不允许你还没有了解哈希表、哈希桶、哈希冲突的解决,如何避免冲突

✏️✏️✏️今天给各位带来的是哈希桶、哈希冲突方面的知识。 清风的CSDN博客 &#x1f61b;&#x1f61b;&#x1f61b;希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01; 动动你们发财的小手&#xff0c;点…

实用篇-ES-DSL查询文档

数据的存储不是目的&#xff0c;我们希望从海量的酒店数据中检索出需要的信息&#xff0c;这就是ES的搜索功能 官方文档: https://elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html#query-dsl。DSL是用来查询文档的 Elasticsearch提供了基于JSON的DSL来定…

vite vue3配置eslint和prettier以及sass

准备 教程 安装eslint 官网 vue-eslint ts-eslint 安装eslint yarn add eslint -D生成配置文件 npx eslint --init安装其他插件 yarn add -D eslint-plugin-import eslint-plugin-vue eslint-plugin-node eslint-plugin-prettier eslint-config-prettier eslint-plugin…

【数据结构】图的存储结构及实现(邻接表和十字链表)

一.邻接矩阵的空间复杂度 假设图G有n个顶点e条边&#xff0c;则存储该图需要O&#xff08;n^2) 不适用稀疏图的存储 二.邻接表 1.邻接表的存储思想&#xff1a; 对于图的每个顶点vi&#xff0c;将所有邻接于vi的顶点链成一个单链表&#xff0c;称为顶点vi的边表&#xff08…

C/C++ 运用VMI接口查询系统信息

Windows Management Instrumentation&#xff08;WMI&#xff09;是一种用于管理和监视Windows操作系统的框架。它为开发人员、系统管理员和自动化工具提供了一种标准的接口&#xff0c;通过这个接口&#xff0c;可以获取有关计算机系统硬件、操作系统和应用程序的信息&#xf…

PS学习笔记——新建文档/修改文档

文章目录 新建文档文档属性像素/分辨率颜色模式背景内容高级选项存储预设 修改文档 新建文档 方法一&#xff1a;ctrlN快捷键可直接打开新建文档界面 方法二&#xff1a;点击菜单栏中 文件->新建&#xff0c;即可打开新建文档界面 文档参数可按需调节(标题可以提前设定或者…

face_recognition:高准确率、简单易用的人脸识别库 | 开源日报 No.79

ageitgey/face_recognition Stars: 49.8k License: MIT 这个项目是一个使用 Python 编写的人脸识别库&#xff0c;可以从图片中识别和操作人脸。它基于 dlib 开发&#xff0c;并采用深度学习技术构建了最先进的人脸识别模型&#xff0c;在 Labeled Faces in the Wild 数据集上…

Redis(消息队列Stream)

Stream是一个轻量级的消息队列。 Redis中Stream的作用是提供一种高效的消息传递机制&#xff0c;允许多个消费者并行地消费消息&#xff0c;并且不会重复消费已经处理过的消息。它可以用于实现分布式任务队列、日志收集、实时数据处理等场景。Redis中的Stream支持多个消费者组…

Python数据分析实战① Python实现数据可视化

文章目录 一、数据可视化介绍二、matplotlib和pandas画图1.matplotlib简介和简单使用2.matplotlib常见作图类型3.使用pandas画图4.pandas中绘图与matplotlib结合使用 三、订单数据分析展示四、Titanic灾难数据分析显示 一、数据可视化介绍 数据可视化是指将数据放在可视环境中…