JUC并发工具-CAS机制

news2024/9/28 13:29:01

面试的时候经常被问到锁、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操作失败。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

wpf devexpress自定义编辑器

打开前一个例子 步骤1-自定义FirstName和LastName编辑器字段 如果运行程序&#xff0c;会通知编辑器是空。对于例子&#xff0c;这两个未命名编辑器在第一个LayoutItem(Name)。和最终用户有一个访客左右编辑器查阅到First Name和Last Name字段&#xff0c;分别。如果你看到Go…

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…

「Verilog学习笔记」数据选择器实现逻辑电路

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 分析 将变量A、B接入4选1数据选择器选择输入端S0 S1。将变量C分配在数据输入端。从表中可以看出输出L与变量C的关系。 当AB00时选通D0而此时L0&#xff0c;所以数据端D0接0…

springboot项目yml文件中使用${}配置

1、传统写法 &#xff08;1&#xff09;配置服务启动端口 # 服务端口 server:port: 9898 &#xff08;2&#xff09;使用idea启动 &#xff08;3&#xff09;使用jar包启动 2、使用${}写法 格式&#xff1a;${自定义参数名:默认值} 作用&#xff1a; 项目启动时动态配置变量…

深度学习数据集—文本、数字、文字识别大合集

最近收集了一大波关于文本、数字识别相关的数据集&#xff0c;有数字识别、也有语言文字识别&#xff0c;废话不多说现在分享给大家&#xff01;&#xff01; 1、500张手写拼音数据集 500张手写拼音数据集&#xff0c;包含对应txt格式标注及图片&#xff0c;&#xff0c;并提…

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

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

LLM大模型量化原理

大型语言模型&#xff08;LLM&#xff09;可以用于文本生成、翻译、问答任务等。但是&#xff0c;LLM 也非常大&#xff08;显然&#xff0c;大型语言模型&#xff09;并且需要大量内存。 这对于手机和平板电脑等小型设备来说可能具有挑战性。 可以将参数乘以所选的精度大小以…

手撕单链表(C语言)

目录 1.单链表的物理结构 2.头文件的实现 3.SList.c文件的实现 3.1尾插、创建节点 3.2打印 3.3头插 3.4尾删 3.5头删 3.6查找 3.7指定位置之前插入数据 3.8指定位置之后插入数据 3.9删除指定位置节点 3.10删除pos之后的节点 3.11销毁链表 4 所有的代码 1.单链表的物理结构 众所…

6.8完全二叉树的节点个数(LC222-E)

算法&#xff1a; 如果不考虑完全二叉树的特性&#xff0c;直接把完全二叉树当作普通二叉树求节点数&#xff0c;其实也很简单。 递归法&#xff1a; 用什么顺序遍历都可以。 比如后序遍历&#xff08;LRV&#xff09;&#xff1a;不断遍历左右子树的节点数&#xff0c;最后…

小程序授权获取昵称

wxml: <form bindsubmit"formsubmit"><view style"width: 90%;display: flex;margin-left: 5%;"><view class"text1">昵称&#xff1a;</view><input style"width: 150px;margin-left: 30px;margin-top: 30px;…

【论文阅读笔记】Supervised Contrastive Learning

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

【LeetCode刷题-滑动窗口】--992.K个不同整数的子数组

992.K个不同整数的子数组 思路&#xff1a; class Solution {public int subarraysWithKDistinct(int[] nums, int k) {return atMostKDistinct(nums,k) - atMostKDistinct(nums,k-1);}//最多包含K个不同整数的子区间个数private int atMostKDistinct(int[] a,int k){int len …

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

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

反激变压器计算方法_笔记

反激变压器计算方法_笔记 匝数比原边电感选定磁芯线圈匝数线径 原视频链接 匝数比 5V 是想要得到的输出电压 0.7V为二极管导通的压降 185Vx根号2是有效值 最大占空比取0.4。得出最小匝数为30。 更改某些值可能得出来的匝数比就不一定是30了&#xff0c; 这其实也是反激变压器…

mac苹果电脑需要安装杀毒软件吗?

随着数字时代的发展&#xff0c;计算机安全问题变得越来越重要。而在计算机安全领域中&#xff0c;杀毒软件是一个被广泛讨论的话题。苹果电脑需要安装杀毒软件吗&#xff1f;对于苹果电脑用户来说&#xff0c;他们常常会疑惑自己是否需要安装杀毒软件来保护自己的电脑。本文将…

力扣每日一题-数位和相等数对的最大和-2023.11.18

力扣每日一题&#xff1a;数位和相等数对的最大和 开篇 这道每日一题还是挺需要思考的&#xff0c;我绕晕了好久&#xff0c;根据题解的提示才写出来。 题目链接:2342.数位和相等数对的最大和 题目描述 代码思路 1.创建一个数组存储每个数位的数的最大值&#xff0c;创建一…

kubernetes学习笔记-概念

参考&#xff1a;https://kubernetes.io/zh-cn/docs/concepts/overview/ 概述 Kubernetes 是一个可移植、可扩展的开源平台&#xff0c;用于管理容器化的工作负载和服务&#xff0c;可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态&#xff0c;其服务、…

【Linux】C文件系统详解(二)——什么是fd文件描述符以及理解“一切皆文件“

文章目录 fd-文件描述符如何深度理解"一切皆文件"**我们使用OS的本质:**FILEFILE是什么?谁提供的?和我们刚刚讲的内核的struct有关系吗FILE是一个结构体.该结构体内部一定要有以下字段:FILE是C语言标准库提供的.FILE和我们刚刚讲的内核的struct没有关系,最多就是上…