Java实现CAS的原理

文章目录

  • 1、 什么是CAS
  • 2、CAS的原理
  • 3、CAS的应用场景
  • 4、Java中的CAS实现
  • 5、使用AtomicInteger实现线程安全的计数器
  • 6、CAS实现原子操作的三大问题
    • 6.1、ABA问题
    • 6.2、循环时间长
    • 6.3、只能保证一个共享变量的原子性
  • 7、总结

1、 什么是CAS

CAS(Compare and Swap)是一种并发编程中的技术,用于实现多线程之间的原子操作。它允许你比较一个内存位置的值和一个预期的值,如果相等,则将新值设置到该内存位置,从而实现原子性的修改操作。CAS是乐观锁,线程执行的时候不会加锁,假设没有冲突去完成某项操作,如果因为冲突失败了就重试,最后直到成功为止。

2、CAS的原理

CAS操作主要包括两个步骤:

比较:首先,将内存位置的当前值与预期值进行比较。
交换:如果当前值与预期值相等,就将新值设置到内存位置。
在这里插入图片描述
前面提到,CAS是一种原子操作。那么Java是怎样来使用CAS的呢?我们知道,在Java中,如果一个方法是native的,那Java就不负责具体实现它,而是交给底层的JVM使用c或者c++去实现。

在Java中,有一个Unsafe类,它在sun.misc包中。它里面是一些native方法,其中就有几个关于CAS的:

boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
boolean compareAndSwapInt(Object o, long offset,int expected,int x);
boolean compareAndSwapLong(Object o, long offset,long expected,long x);

当然,他们都是public native的。

Unsafe中对CAS的实现是C++写的,它的具体实现和操作系统、CPU都有关系。

Linux的X86下主要是通过cmpxchgl这个指令在CPU级完成CAS操作的,但在多处理器情况下必须使用lock指令加锁来完成。当然不同的操作系统和处理器的实现会有所不同,大家可以自行了解。

当然,Unsafe类里面还有其它方法用于不同的用途。比如支持线程挂起和恢复的park和unpark, LockSupport类底层就是调用了这两个方法。还有支持反射操作的allocateInstance()方法。

3、CAS的应用场景

线程安全问题:CAS可以用于解决多线程环境下的数据竞争问题,如计数器、标志位等。
性能优化:CAS避免了使用锁的开销,适用于一些高并发、频繁修改的场景。

4、Java中的CAS实现

Java中提供了java.util.concurrent.atomic包,包含了一系列基于CAS的原子类,如AtomicInteger、AtomicLong等。

5、使用AtomicInteger实现线程安全的计数器

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger value = new AtomicInteger(0);

    public void increment() {
        value.incrementAndGet();
    }

    public int getValue() {
        return value.get();
    }
}

为什么AtomicInteger就是原子性的操作,这里我们以AtomicInteger类的getAndAdd(int delta)方法为例,来看看Java是如何实现原子操作的。

public final int getAndAdd(int delta) {
    return U.getAndAddInt(this, VALUE, delta);
}

这里的U其实就是Unsafe对象;

6、CAS实现原子操作的三大问题

尽管CAS在很多情况下是非常有用的,但它也存在三大经典问题:ABA问题、循环时间长以及只能保证一个共享变量的原子性。

6.1、ABA问题

ABA问题指的是,在CAS操作期间,某个值从A变为B,然后再变回A,而CAS操作的结果可能会错误地判定为成功。这是因为CAS只比较当前值和预期值,不考虑中间是否发生过其他修改。解决ABA问题的方法是引入版本号,即将共享变量的值和版本号一起进行比较和交换。

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABAExample {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> atomicStampedRef =
            new AtomicStampedReference<>(1, 0);

        int initialStamp = atomicStampedRef.getStamp();
        int initialValue = atomicStampedRef.getReference();

        // 线程A执行操作,此时线程B修改了值,又恢复原值
        atomicStampedRef.compareAndSet(initialValue, 2, initialStamp, initialStamp + 1);
        
        // 线程C试图修改值,但由于版本号不匹配,CAS失败
        boolean success = atomicStampedRef.compareAndSet(initialValue, 3, initialStamp, initialStamp + 1);
    }
}

6.2、循环时间长

CAS操作是基于循环实现的,即在操作失败时会重试,直至操作成功为止。但当竞争激烈时,可能会导致循环时间较长,浪费CPU资源。解决这个问题的方法是在重试时加入一定的时间限制,避免无限循环。

import java.util.concurrent.atomic.AtomicInteger;

public class LongLoopExample {
    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger(0);
        
        int expectedValue = 0;
        int newValue = 1;
        int maxRetries = 10;
        
        int retries = 0;
        while (retries < maxRetries) {
            if (counter.compareAndSet(expectedValue, newValue)) {
                // CAS成功,退出循环
                break;
            }
            retries++;
        }
    }
}

6.3、只能保证一个共享变量的原子性

CAS操作只能保证单个共享变量的原子性,不能解决多个共享变量之间的原子操作问题。这意味着如果需要同时修改多个共享变量,CAS可能无法保证这些操作的原子性。

import java.util.concurrent.atomic.AtomicInteger;

public class MultipleVariablesExample {
    public static void main(String[] args) {
        AtomicInteger value1 = new AtomicInteger(0);
        AtomicInteger value2 = new AtomicInteger(0);
        
        value1.incrementAndGet(); // CAS保证了value1的原子操作
        value2.incrementAndGet(); // 但不能保证value1和value2操作的原子性
    }
}

7、总结

虽然CAS存在这三大问题,但在很多情况下仍然是非常有用的,并且在并发编程中发挥着重要的作用。为了解决这些问题,有时需要结合其他技术,如引入版本号来解决ABA问题,或者使用锁来处理多个共享变量之间的复杂原子操作。

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

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

相关文章

读书笔记-《ON JAVA 中文版》-摘要22[第二十章 泛型-1]

文章目录 第二十章 泛型1. 简单泛型1.1 简单泛型1.2 一个元组类库 2. 泛型接口3. 泛型方法3.1 泛型方法3.2 变长参数和泛型方法 4. 构建复杂模型 第二十章 泛型 普通的类和方法只能使用特定的类型&#xff1a;基本数据类型或类类型。如果编写的代码需要应用于多种类型&#xff…

5G+AI数字化智能工厂建设解决方案PPT

导读&#xff1a;原文《5GAI数字化智能工厂建设解决方案》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。数字化智能工厂定义 智能基础架构协同框架 - 端、边、云、网…

ARM 作业1

一、思维导图 二、 1. 2. .text 文本段 .globl _start 声明_start:mov r0,#0mov r1,#0fun:cmp r1,#100bhi stopadd r0,r0,r1add r1,r1,#1b fun stop:b stop .end

【3Ds Max】车削命令的简单使用(以制作花瓶为例)

简介 在3ds Max中&#xff0c;"车削"&#xff08;Lathe&#xff09;是一种建模命令&#xff0c;用于创建围绕轴线旋转的几何形状。通过车削命令&#xff0c;您可以将一个闭合的平面或曲线几何形状旋转&#xff0c;从而生成一个立体对象。这种方法常用于创建圆柱体、…

原生微信小程序自定义picker多列选择器:picker写法用法

前言: 最近用原生微信小程序写法写医疗相关项目微信小程序&#xff0c;在编辑个人资料的时候&#xff0c;需要很多选择器&#xff0c;比如城市地区选择器&#xff0c;职业职称选择器&#xff0c;科室选择器&#xff0c;学校选择器&#xff0c;学历选择器&#xff0c;年份日期选…

YOLOv5改进系列(21)——替换主干网络之RepViT(清华 ICCV 2023|最新开源移动端ViT)

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制 YOLOv5改进系列(2

基于YOLOv8模型的五类动物目标检测系统(PyTorch+Pyside6+YOLOv8模型)

摘要&#xff1a;基于YOLOv8模型的五类动物目标检测系统可用于日常生活中检测与定位动物目标&#xff08;狼、鹿、猪、兔和浣熊&#xff09;&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的目标检测&#xff0c;另外本系统还支持图片、视频等格式的结果可视化与…

如何区分闰年与平年

首先要明白 地球绕太阳运行周期为365天5小时48分46秒&#xff08;合365.24219天&#xff09;&#xff0c;即一回归年&#xff08;tropical year&#xff09;。公历的平年只有365日&#xff0c;比回归年短约0.2422 日&#xff0c;每四年累积约一天&#xff0c;把这一天加于2月末…

CentOS7源码安装MySQL详细教程

&#x1f60a; 作者&#xff1a; Eric &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_47316183?typeblog &#x1f389; 主题&#xff1a;CentOS7源码安装MySQL详细教程 ⏱️ 创作时间&#xff1a; 2023年08月014日 文章目录 1、安装的四种方式2、源码安装…

机器学习基础之《分类算法(3)—模型选择与调优》

作用是如何选择出最好的K值 一、什么是交叉验证&#xff08;cross validation&#xff09; 1、定义 交叉验证&#xff1a;将拿到的训练数据&#xff0c;分为训练和验证集。以下图为例&#xff1a;将数据分成5份&#xff0c;其中一份作为验证集。然后经过5次(组)的测试&#x…

nginx php-fpm安装配置

nginx php-fpm安装配置 nginx本身不能处理PHP&#xff0c;它只是个web服务器&#xff0c;当接收到请求后&#xff0c;如果是php请求&#xff0c;则发给php解释器处理&#xff0c;并把结果返回给客户端。 nginx一般是把请求发fastcgi管理进程处理&#xff0c;fascgi管理进程选…

musl libc ldso 动态加载研究笔记:02

前言 本篇继续研究 musl libc ldso 的动态加载过程中遇到的关键性的概念&#xff1a;到底要加载ELF 文件的哪些内容到 内存 当前如果遇到 ELF 动态加载&#xff0c;当前系统需要有【文件系统】&#xff0c;并且有较大的内存&#xff0c;因为 ELF 文件是无法直接运行的&#xf…

【解析postman工具的使用---基础篇】

postman前端请求详解 主界面1.常见类型的接口请求1.1 查询参数的接口请求1.1.1 什么是查询参数?1.1.2 postman如何请求 1.2 ❤表单类型的接口请求1.2.1 复习下http请求1.2.2❤ 什么是表单 1.3 上传文件的表单请求1.4❤ json类型的接口请求 2. 响应接口数据分析2.1 postman的响…

Qt应用开发(基础篇)——MDI窗口 QMdiArea QMdiSubWindow

一、前言 QMdiArea类继承于QAbstractScrollArea&#xff0c;QAbstractScrollArea继承于QFrame&#xff0c;是Qt用来显示MDI窗口的部件。 滚屏区域基类 QAbstractScrollAreahttps://blog.csdn.net/u014491932/article/details/132245486 框架类 QFramehttps://blog.csdn.net/u01…

【Alibaba中间件技术系列】「RocketMQ技术专题」让我们一起探索一下DefaultMQPushConsumer的实现原理及源码分析

RocketMQ开源是使用文件作为持久化工具&#xff0c;阿里内部未开源的性能会更高&#xff0c;使用oceanBase作为持久化工具。 在RocketMQ1.x和2.x使用zookeeper管理集群&#xff0c;3.x开始使用nameserver代替zk&#xff0c;更轻量级&#xff0c;此外RocketMQ的客户端拥有两种的…

公网远程连接Redis数据库「内网穿透」

文章目录 1. Linux(centos8)安装redis数据库2. 配置redis数据库3. 内网穿透3.1 安装cpolar内网穿透3.2 创建隧道映射本地端口 4. 配置固定TCP端口地址4.1 保留一个固定tcp地址4.2 配置固定TCP地址4.3 使用固定的tcp地址连接 前言 洁洁的个人主页 我就问你有没有发挥&#xff0…

GuLi商城-前端基础Vue-生命周期和钩子函数

下图展示了实例的生命周期。你不需要立马弄明白所有的东西&#xff0c;不过随着你的不断学习和使用&#xff0c;它 的参考价值会越来越高。 VUE 的生命周期指的是组件在创建、运行和销毁过程中所经历的一系列事件&#xff0c;通过这些事件可以 让开发者在不同阶段进行相应的…

【C语言】指针的进阶

目录 一、字符指针 二、指针数组 三、数组指针 1.数组指针的定义 2.&数组名和数组名区别 3.数组指针的使用 四、数组参数与指针参数 1.一维数组传参 2.二维数组传参 3.一级指针传参 4.二级指针传参 五、函数指针 六、函数指针数组 七、指向函数指针数组的指针…

【使用教程】在Ubuntu下运行CANopen通信PMM伺服电机使用教程(NimServoSDK_V2.0.0)

本教程将指导您在Ubuntu操作系统下使用NimServoSDK_V2.0.0来运行CANopen通信的PMM系列一体化伺服电机。我们将介绍必要的步骤和命令&#xff0c;以确保您能够成功地配置和控制PMM系列一体化伺服电机。 NimServoSDK_V2.0.0是一款用于PMM一体化伺服电机的软件开发工具包。它提供了…

金融语言模型:FinGPT

项目简介 FinGPT是一个开源的金融语言模型&#xff08;LLMs&#xff09;&#xff0c;由FinNLP项目提供。这个项目让对金融领域的自然语言处理&#xff08;NLP&#xff09;感兴趣的人们有了一个可以自由尝试的平台&#xff0c;并提供了一个与专有模型相比更容易获取的金融数据。…