CAS 详解

Java 中 CAS 是如何实现的?

在 Java 中,实现 CAS(Compare-And-Swap, 比较并交换)操作的一个关键类是Unsafe

Unsafe类位于sun.misc包下,是一个提供低级别、不安全操作的类。由于其强大的功能和潜在的危险性,它通常用于 JVM 内部或一些需要极高性能和底层访问的库中,而不推荐普通开发者在应用程序中使用。

sun.misc包下的Unsafe类提供了compareAndSwapObjectcompareAndSwapIntcompareAndSwapLong方法来实现的对Objectintlong类型的 CAS 操作:

/*** 以原子方式更新对象字段的值。** @param o        要操作的对象* @param offset   对象字段的内存偏移量* @param expected 期望的旧值* @param x        要设置的新值* @return 如果值被成功更新,则返回 true;否则返回 false*/
boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);/*** 以原子方式更新 int 类型的对象字段的值。*/
boolean compareAndSwapInt(Object o, long offset, int expected, int x);/*** 以原子方式更新 long 类型的对象字段的值。*/
boolean compareAndSwapLong(Object o, long offset, long expected, long x);

Unsafe类中的 CAS 方法是native方法。native关键字表明这些方法是用本地代码(通常是 C 或 C++)实现的,而不是用 Java 实现的。这些方法直接调用底层的硬件指令来实现原子操作。也就是说,Java 语言并没有直接用 Java 实现 CAS。

更准确点来说,Java 中 CAS 是 C++ 内联汇编的形式实现的,通过 JNI(Java Native Interface) 调用。因此,CAS 的具体实现与操作系统以及 CPU 密切相关。

java.util.concurrent.atomic 包提供了一些用于原子操作的类。

Atomic 类依赖于 CAS 乐观锁来保证其方法的原子性,而不需要使用传统的锁机制(如 synchronized 块或 ReentrantLock)。

AtomicInteger是 Java 的原子类之一,主要用于对 int 类型的变量进行原子操作,它利用Unsafe类提供的低级别原子操作方法实现无锁的线程安全性。

下面,我们通过解读AtomicInteger的核心源码(JDK1.8),来说明 Java 如何使用Unsafe类的方法来实现原子操作。

AtomicInteger核心源码如下:

// 获取 Unsafe 实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;static {try {// 获取“value”字段在AtomicInteger类中的内存偏移量valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }
}
// 确保“value”字段的可见性
private volatile int value;// 如果当前值等于预期值,则原子地将值设置为newValue
// 使用 Unsafe#compareAndSwapInt 方法进行CAS操作
public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}// 原子地将当前值加 delta 并返回旧值
public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta);
}// 原子地将当前值加 1 并返回加之前的值(旧值)
// 使用 Unsafe#getAndAddInt 方法进行CAS操作。
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);
}// 原子地将当前值减 1 并返回减之前的值(旧值)
public final int getAndDecrement() {return unsafe.getAndAddInt(this, valueOffset, -1);
}

 Unsafe#getAndAddInt源码:

// 原子地获取并增加整数值
public final int getAndAddInt(Object o, long offset, int delta) {int v;do {// 以 volatile 方式获取对象 o 在内存偏移量 offset 处的整数值v = getIntVolatile(o, offset);} while (!compareAndSwapInt(o, offset, v, v + delta));// 返回旧值return v;
}

 

可以看到,getAndAddInt 使用了 do-while 循环:在compareAndSwapInt操作失败时,会不断重试直到成功。也就是说,getAndAddInt方法会通过 compareAndSwapInt 方法来尝试更新 value 的值,如果更新失败(当前值在此期间被其他线程修改),它会重新获取当前值并再次尝试更新,直到操作成功。

由于 CAS 操作可能会因为并发冲突而失败,因此通常会与while循环搭配使用,在失败后不断重试,直到操作成功。这就是 自旋锁机制

CAS 算法存在哪些问题?

ABA 问题是 CAS 算法最常见的问题。

ABA 问题

如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然是 A 值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回 A,那 CAS 操作就会误认为它从来没有被修改过。这个问题被称为 CAS 操作的 "ABA"问题。

ABA 问题的解决思路是在变量前面追加上版本号或者时间戳。JDK 1.5 以后的 AtomicStampedReference 类就是用来解决 ABA 问题的,其中的 compareAndSet() 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

public boolean compareAndSet(V   expectedReference,V   newReference,int expectedStamp,int newStamp) {Pair<V> current = pair;returnexpectedReference == current.reference &&expectedStamp == current.stamp &&((newReference == current.reference &&newStamp == current.stamp) ||casPair(current, Pair.of(newReference, newStamp)));
}

循环时间长开销大

CAS 经常会用到自旋操作来进行重试,也就是不成功就一直循环执行直到成功。如果长时间不成功,会给 CPU 带来非常大的执行开销。

如果 JVM 能够支持处理器提供的pause指令,那么自旋操作的效率将有所提升。pause指令有两个重要作用:

  1. 延迟流水线执行指令pause指令可以延迟指令的执行,从而减少 CPU 的资源消耗。具体的延迟时间取决于处理器的实现版本,在某些处理器上,延迟时间可能为零。
  2. 避免内存顺序冲突:在退出循环时,pause指令可以避免由于内存顺序冲突而导致的 CPU 流水线被清空,从而提高 CPU 的执行效率。

只能保证一个共享变量的原子操作

CAS 操作仅能对单个共享变量有效。当需要操作多个共享变量时,CAS 就显得无能为力。不过,从 JDK 1.5 开始,Java 提供了AtomicReference类,这使得我们能够保证引用对象之间的原子性。通过将多个变量封装在一个对象中,我们可以使用AtomicReference来执行 CAS 操作。

除了 AtomicReference 这种方式之外,还可以利用加锁来保证。

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

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

相关文章

九识智能与徐工汽车达成战略合作,共绘商用车未来新蓝图

近日&#xff0c;九识智能与徐工汽车签署战略合作协议&#xff0c;标志着双方在智能驾驶技术与新能源商用车融合应用、联合生产及市场推广等方面迈入深度合作的新篇章&#xff0c;将共同引领智能驾驶技术商业化浪潮。 近年来&#xff0c;在国家智能化发展战略的引领下&#xff…

【vue2.7.16系列】手把手教你搭建后台系统__登录使用状态管理(15)

使用store进行登录信息管理 其实就是把登录放到vuex的actions中去执行&#xff0c;然后保存用户信息、权限等 在store/modules/account.js中添加如下代码&#xff1a; import { login, logout, getInfo, menusApi } from /api/account; // getExpiresTime import {getToken,s…

sql报错信息将字符串转换为 uniqueidentifier 时失败

报错信息&#xff1a; [42000] [Microsoft][SQL Server Native Client 10.0][SQL Server]将字符串转换为 uniqueidentifier 时失败 出错行如下&#xff1a; 表A.SourceCode 表B.ID 出错原因&#xff1a; SourceCode是nvarchar,但ID是uniqueidentifier 数据库查询字段和类…

「Mac畅玩鸿蒙与硬件22」鸿蒙UI组件篇12 - Canvas 组件的动态进阶应用

在鸿蒙应用中&#xff0c;Canvas 组件可以实现丰富的动态效果&#xff0c;适合用于动画和实时更新的场景。本篇将介绍如何在 Canvas 中实现动画循环、动态进度条、旋转和缩放动画&#xff0c;以及性能优化策略。 关键词 Canvas 组件动态绘制动画效果动态进度条旋转和缩放性能优…

Python练习10

Python日常练习 题目&#xff1a; 编写程序&#xff0c;输出如下所示图案。 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 要求&#xff1a; 使用for循环的方式完成 --------------------------------------------------------- 注意&#xff1a; …

【前端】html的8个常用标签

HTML html 超文本链接(标记)语言 H5 HTML v5 get/post/delete/put —— restful 网络规划 Web开发 结构样式动作 架构 装饰 交互&#xff08;动作&#xff09; 装饰做好了–> UI工程师 标签 文本相关 图片、图像、声音 导航 表格* 列表 表单标签* 布局标签 H5…

Java高效学习家教平台系统小程序源码

&#x1f4da; 家教平台系统&#xff1a;让孩子学习更高效的秘密武器 &#x1f680; &#x1f469;‍&#x1f3eb; 引言&#xff1a;家教新风尚&#xff0c;线上平台引领教育潮流 在这个信息爆炸的时代&#xff0c;家教平台系统如同雨后春笋般涌现&#xff0c;为孩子们的学习…

Qt多边形填充/不填充绘制

1 填充多边形绘制形式 void GraphicsPolygonItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {Q_UNUSED(option);Q_UNUSED(widget);//painter->setPen(pen()); // 设置默认画笔//painter->setBrush(brush()); // 设置默…

OpenAI大事记;GPT到ChatGPT参数量进化

目录 OpenAI大事记 GPT到ChatGPT参数量进化 OpenAI大事记 GPT到ChatGPT参数量进化 ChatGPT是从初代 GPT逐渐演变而来的。在进化的过程中,GPT系列模型的参数数量呈指数级增长,从初代GPT的1.17亿个参数,到GPT-2的15 亿个参数,再到 GPT-3的1750 亿个参数。模型越来越大,训练…

一文了解Java序列化

Java 序列化&#xff08;Serialization&#xff09;是将对象的状态转换为字节流&#xff0c;以便将对象的状态保存到文件中或通过网络传输的过程。反序列化&#xff08;Deserialization&#xff09;则是将字节流恢复为原始对象。Java 序列化主要通过 Serializable 接口实现。 为…

vue解决跨域问题

1、在vue项目的根目录创建vue.config.js的文件 复制以下带代码 devServer: {proxy: {/api: {target: http://localhost:3000, // 目标服务器地址changeOrigin: true, // 是否改变源pathRewrite: {^/api: // 重写路径&#xff0c;例如将/api/user重写为/user}}}}2、将接口的地…

是时候用开源降低AI落地门槛了

过去三十多年&#xff0c;从Linux到KVM&#xff0c;从OpenStack到Kubernetes&#xff0c;IT领域众多关键技术都来自开源。开源技术不仅大幅降低了IT成本&#xff0c;也降低了企业技术创新的门槛。 那么&#xff0c;在生成式AI时代&#xff0c;开源能够为AI带来什么&#xff1f;…

基于SSM+VUE守护萌宠宠物网站JAVA|VUE|Springboot计算机毕业设计源代码+数据库+LW文档+开题报告+答辩稿+部署教+代码讲解

源代码数据库LW文档&#xff08;1万字以上&#xff09;开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统&#xff1a;Window操作系统 2、开发工具&#xff1a;IntelliJ IDEA或者Eclipse 3、数据库存储&#xff1a…

Oh My Posh安装

nullSet up your terminalhttps://ohmyposh.dev/docs/installation/windows Git ee oh-my-posh: Windows上的oh-my-zsh&#xff0c;源地址 https://github.com/JanDeDobbeleer/oh-my-posh.git (gitee.com)https://gitee.com/efluent/oh-my-posh

基于Spring Boot+Vue的助农销售平台(协同过滤算法、限流算法、支付宝沙盒支付、实时聊天、图形化分析)

&#x1f388;系统亮点&#xff1a;协同过滤算法、节流算法、支付宝沙盒支付、图形化分析、实时聊天&#xff1b; 一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk1…

Webserver(4.8)UDP、广播、组播

目录 UDP通信server.cclient.c 广播client.cserver.c 组播client.cserver.c UDP通信 server.c #include<arpa/inet.h> #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> int main(){//1.创建一个通信的socketint f…

大数据集群中实用的三个脚本文件解析与应用

目录 一、jps - cluster.sh 脚本文件 &#xff08;一&#xff09;背景与功能 &#xff08;二&#xff09;使用方法 二、集群文件分发脚本 xsync.sh &#xff08;一&#xff09;背景与问题 &#xff08;二&#xff09;功能与实现原理 &#xff08;三&#xff09;脚本编写…

普通人没钱又没能力的话,那就踏实学一门手艺

其实想在这个社会上谋生有很多种方式&#xff0c;大致可以分为这么几个类型。 ​ 1&#xff1a;劳动型 大多数人无疑都是从事的劳动型工作&#xff0c;靠出卖体力挣钱&#xff0c;如建筑工人、快递员、服务员&#xff0c;车间杂工等等。这种性质的工作比较累&#xff0c;性价…

数据管理的四大支柱:揭秘数据中台、数据仓库、数据治理和主数据

文章目录 一、数据中台&#xff1a;数据的“中央厨房”二、数据仓库&#xff1a;数据的“图书馆”三、数据治理&#xff1a;数据的“交警”四、主数据&#xff1a;数据的“身份证”五、定位与差异&#xff1a;协同作战的团队成员 在数字化时代&#xff0c;企业数据管理变得至关…

RabbitMQ 的集群

大家好&#xff0c;我是锋哥。今天分享关于【RabbitMQ 的集群】面试题&#xff1f;希望对大家有帮助&#xff1b; RabbitMQ 的集群 RabbitMQ 是一种流行的开源消息代理&#xff0c;广泛用于构建分布式系统中的消息队列。随着应用程序规模的扩大&#xff0c;单一的 RabbitMQ 实…