面试必问的 CAS ,要多了解

2023-11-20

前言

CAS(Compare and Swap),即比较并替换,实现并发算法时常用到的一种技术,Doug lea大神在java同步器中大量使用了CAS技术,鬼斧神工的实现了多线程执行的安全性。

CAS的思想很简单:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。

问题

一个n++的问题。

1
2
3
4
5
6
7
8
public class Case {
 
     public volatile int n;
 
     public void add() {
         n++;
     }
}

通过javap -verbose Case看看add方法的字节码指令

1
2
3
4
5
6
7
8
9
10
11
public void add();
     flags: ACC_PUBLIC
     Code:
       stack= 3 , locals= 1 , args_size= 1
          0 : aload_0      
          1 : dup          
          2 : getfield      # 2                  // Field n:I
          5 : iconst_1     
          6 : iadd         
          7 : putfield      # 2                  // Field n:I
         10 : return

n++被拆分成了几个指令:

  1. 执行getfield拿到原始n;
  2. 执行iadd进行加1操作;
  3. 执行putfield写把累加后的值写回n;

通过volatile修饰的变量可以保证线程之间的可见性,但并不能保证这3个指令的原子执行,在多线程并发执行下,无法做到线程安全,得到正确的结果,那么应该如何解决呢?

如何解决

在add方法加上synchronized修饰解决。

1
2
3
4
5
6
7
8
public class Case {
 
     public volatile int n;
 
     public synchronized void add() {
         n++;
     }
}

这个方案当然可行,但是性能上差了点,还有其它方案么?

再来看一段代码

1
2
3
4
5
6
7
8
public int a = 1 ;
public boolean compareAndSwapInt( int b) {
     if (a == 1 ) {
         a = b;
         return true ;
     }
     return false ;
}

如果这段代码在并发下执行,会发生什么?

假设线程1和线程2都过了a==1的检测,都准备执行对a进行赋值,结果就是两个线程同时修改了变量a,显然这种结果是无法符合预期的,无法确定a的最终值。

解决方法也同样暴力,在compareAndSwapInt方法加锁同步,变成一个原子操作,同一时刻只有一个线程才能修改变量a。

除了低性能的加锁方案,我们还可以使用JDK自带的CAS方案,在CAS中,比较和替换是一组原子操作,不会被外部打断,且在性能上更占有优势。

下面以AtomicInteger的实现为例,分析一下CAS是如何实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AtomicInteger extends Number implements java.io.Serializable {
     // setup to use Unsafe.compareAndSwapInt for updates
     private static final Unsafe unsafe = Unsafe.getUnsafe();
     private static final long valueOffset;
 
     static {
         try {
             valueOffset = unsafe.objectFieldOffset
                 (AtomicInteger. class .getDeclaredField( "value" ));
         } catch (Exception ex) { throw new Error(ex); }
     }
 
     private volatile int value;
     public final int get() { return value;}
}
  1. Unsafe,是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。
  2. 变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
  3. 变量value用volatile修饰,保证了多线程之间的内存可见性。

看看AtomicInteger如何实现并发下的累加操作:

1
2
3
4
5
6
7
8
9
10
11
12
public final int getAndAdd( int delta) {   
     return unsafe.getAndAddInt( this , valueOffset, delta);
}
 
//unsafe.getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {
     int var5;
     do {
         var5 = this .getIntVolatile(var1, var2);
     } while (! this .compareAndSwapInt(var1, var2, var5, var5 + var4));
     return var5;
}

假设线程A和线程B同时执行getAndAdd操作(分别跑在不同CPU上):

  1. AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据Java内存模型,线程A和线程B各自持有一份value的副本,值为3。
  2. 线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。
  3. 线程B也通过getIntVolatile(var1, var2)方法获取到value值3,运气好,线程B没有被挂起,并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为2。
  4. 这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值(3)和内存的值(2)不一致,说明该值已经被其它线程提前修改过了,那只能重新来一遍了。
  5. 重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。

整个过程中,利用CAS保证了对于value的修改的并发安全,继续深入看看Unsafe类中的compareAndSwapInt方法实现。

1
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);

Unsafe类中的compareAndSwapInt,是一个本地方法,该方法的实现位于unsafe.cpp中

1
2
3
4
5
6
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
   UnsafeWrapper( "Unsafe_CompareAndSwapInt" );
   oop p = JNIHandles::resolve(obj);
   jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
   return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
  1. 先想办法拿到变量value在内存中的地址。
  2. 通过Atomic::cmpxchg实现比较替换,其中参数x是即将更新的值,参数e是原内存的值。

如果是Linux的x86,Atomic::cmpxchg方法的实现如下:

1
2
3
4
5
6
7
8
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
   int mp = os::is_MP();
   __asm__ volatile (LOCK_IF_MP(% 4 ) "cmpxchgl %1,(%3)"
                     : "=a" (exchange_value)
                     : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                     : "cc" , "memory" );
   return exchange_value;
}

看到这汇编,内心崩溃

__asm__表示汇编的开始
volatile表示禁止编译器优化
LOCK_IF_MP是个内联函数

1
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

Window的x86实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
     int mp = os::isMP(); //判断是否是多处理器
     _asm {
         mov edx, dest
         mov ecx, exchange_value
         mov eax, compare_value
         LOCK_IF_MP(mp)
         cmpxchg dword ptr [edx], ecx
     }
}
 
// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0  \
                        __asm je L0      \
                        __asm _emit 0xF0 \
                        __asm L0:

LOCK_IF_MP根据当前系统是否为多核处理器决定是否为cmpxchg指令添加lock前缀。

  1. 如果是多处理器,为cmpxchg指令添加lock前缀。
  2. 反之,就省略lock前缀。(单处理器会不需要lock前缀提供的内存屏障效果)

intel手册对lock前缀的说明如下:

  1. 确保后续指令执行的原子性。
  2. 在Pentium及之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其它处理器暂时无法通过总线访问内存,很显然,这个开销很大。在新的处理器中,Intel使用缓存锁定来保证指令执行的原子性,缓存锁定将大大降低lock前缀指令的执行开销。
  3. 禁止该指令与前面和后面的读写指令重排序。
  4. 把写缓冲区的所有数据刷新到内存中。

上面的第2点和第3点所具有的内存屏障效果,保证了CAS同时具有volatile读和volatile写的内存语义。

CAS缺点

CAS存在一个很明显的问题,即ABA问题。

问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?

如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。



http://www.importnew.com/27811.html


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

面试必问的 CAS ,要多了解 的相关文章

  • cas服务器作用,CAS服务器搭建

    CAS服务器搭建 目的 xff1a 搭建以jdbc方式连接数据库并认证用户信息 服务器源码下载地址 https github com apereo cas releases tag v4 2 1 解压后 xff0c 项目目录如下 xff1a
  • CAS服务(5.3)使用mysql验证

    CAS服务使用mysql验证 一 添加相关依赖 在pom文件里添加下面的依赖 这里cas的版本是5 3 14 lt dependency gt lt groupId gt org apereo cas lt groupId gt lt ar
  • 实战:CAS搭建

    一 CAS服务器的搭建 1 下载CAS服务器的源码 xff0c 我下载的是CAS Maven WAR Overlay 的分支4 2 X版本 注 xff1a 如若不想了解查找下载地方过程 xff0c 请直接参见 xff08 3 xff09 的
  • cas + tomcat 配置步骤详细笔记(一)

    首先需要准备资源如下 xff1a cas server 4 0 0 release zip xff0c cas client 2 0 11 zip xff0c apache tomcat 6 0 29 下面操作在dos下操作 xff08 开
  • 使用cas-overlay-template 6.2服务部署到整合cas-client

    1 什么sso是单点登录 单点登录 xff08 Single Sign On xff09 xff0c 简称为 SSO xff0c 是比较流行的企业业务整合的解决方案之一 SSO的定义是在多个应用系统中 xff0c 用户只需要登录一次就可以访
  • CAS 服务端的搭建

    上文讲了CAS客户端 xff0c 本文记录CAS Server的搭建步骤 CAS Server的版本一定要选好 xff0c 我选的是CAS5 3 xff0c Java版本用的8 xff0c 目前最新的CAS6 5的Java版本最低是11了
  • AtomicInteger、Unsafe类、ABA问题

    AtomicInteger Java中的AtomicInteger大家应该很熟悉 它是为了解决多线程访问Integer变量导致结果不正确所设计的一个基于多线程并且支持原子操作的Integer类 AtomicInteger内部有一个变量UnS
  • Cas5.3.2 服务端 自定义登入界面

    第一 项目整体结构 自定义页面涉及资源全部存放再src main resources 文件夹目录下 目录 含义 services 配置自定义登入网站模板 static 静态文件目录 用于存放js css代码的 templates 模板文件目
  • cas单点登录-自定义登录验证(四)

    我们在使用SSO单点登录的时候不只是验证一下用户名和密码是否一致 有时候还需要验证一些别的校验 那么这一张讲一下如何自定义验证器 自定义验证很重要 因为我们后续的很多功能 都是基于自定义验证 CAS服务器的org apereo cas au
  • AtomicStampedReference、AtomicMarkableReference源码分析,解决cas ABA问题

    cas的ABA问题就是 假设初始值为A 线程3和线程1都获取到了初始值A 然后线程1将A改为了B 线程2将B又改回了A 这时候线程3做修改时 是感知不到这个值从A改为了B又改回了A的过程 AtomicStampedReference 本质是
  • CAS AD LDAP 32 错误

    当我尝试使用 CAS 登录时 我看到了这一点 CAS 通过 LDAP 对 AD 进行身份验证 SEVERE Servlet service for servlet cas threw exception javax naming NameN
  • javax.net.ssl.SSLHandshakeException:java.security.cert.CertificateException:不存在主题备用名称

    基本上 我有一个测试服务器 基于 Linux 带有公共 IP 机器人 没有公共主机名 所以我尝试使用 IP 地址为其创建 ssl 证书 这样我的 Java 应用程序就可以使用 IP 地址访问另一个应用程序 例如 https 210 10 1
  • CodeIgniter 的 CAS 身份验证库

    我正在尝试在 CodeIgniter 应用程序中实现 CAS 身份验证 但我找不到当前是否有为其设置的库 我通过只包含类并添加一些肮脏的修复来进行管理 但如果有人知道合适的库 我认为这将是一个更干净的解决方案 我一直在浏览这里以及谷歌上的一
  • 当 Apache Web 服务器使用 mod_jk 连接 Tomcat 时启用 SSL

    I have usr local tomcat webapps cas 我的 java 应用程序正在运行 当我尝试连接 Tomcat 和 Apache Web 服务器 httpd 之后http 192 168 0 117 cas我可以看到登
  • Apache Maven 错误:无法将工件 org.apache.maven.plugins:maven-clean-plugin:pom: 2.5 传输到中央

    我对此很陌生 我正在 apache maven 上工作 我在代理服务器后面工作 每次 我都会尝试构建maven项目 它给了我这个错误 我还更改了 settings xml 文件中的代理设置 但它不起作用 它一直给我同样的问题 构建失败 我在
  • 单点登录以保护 REST API 和基于 Web 的内部系统

    我需要一些关于如何使用单一身份验证系统保护 REST API 和基于 Web 的内部系统的建议 我正在研究使用的可能性 oAuth 2 0 JA SIG CAS Custom Implementation implement two sep
  • 有没有办法有条件地应用注释?

    在我的 java play 应用程序中 我有注释 RequiresAuthentication clientName CasClient 在我的控制器内 我只想在生产环境中对用户进行身份验证 如何有条件地应用注释 如果我处理身份验证的方式是
  • 如何将 AngularJS 路由与 CAS 身份验证重定向一起使用,或者 Angular 不可用?

    我的应用程序通常使用以下路由 http angularapp com page bannanas http angularapp com page bannanas 但是 如果用户未经过身份验证 则用户将被重定向到 CAS 登录页面 然后登
  • jasig cas 重定向过多问题

    我正在尝试使用 spring security 和 spring security cas 带有 Jasig CAS 的 SSO 来保护 spring boot Web 应用程序 尝试访问受保护的资源时 我遇到了太多重定向错误 该项目可用h
  • 通过cas进行ajax调用

    我需要编写一个谷歌小工具来读取谷歌群组的提要 问题是我正在进行 ajax 调用来检索提要 而我们的 google apps 域受 CAS 中央身份验证服务 保护 因此 我在拨打电话时收到 400 错误请求 我怀疑浏览器在进行 ajax 调用

随机推荐

  • Error while parsing UI hierarchy XML file: Invalid ui automator hierarchy file. Error while parsing

    官方的工具就这吊样各种报错 不如使用二次开发的工具 使用 https github com alibaba web editor 替代
  • el-select实现懒加载

    先看一个线上的演示示例 https code juejin cn pen 7273352811440504889 背景 我们在实际开发中经常遇到这样的需求 el select实现懒加载 用通俗的话说 为了增加响应速度 就是初始下拉只展示50
  • 网络流量在线分析系统的设计与实现

    编译环境 visual studio2019 安装并配置winpcap和pthreads库函数 1 配置环境 1 1 安装vscode 参考微信公众号 软件安装管家 1 2 安装MinGW w64 下载地址 添加链接描述 安装参考博客 Mi
  • 实验二 程序流程控制

    1 编写程序计算 1 3 5 7 99 之和 summ 0 for i in range 1 100 summ summ i print 和为 summ 2 编写程序 计算 2 4 6 8 100 之和 summ 0 for i in ra
  • 计算机网络试题

    一 选择题 1 OSI模型与TCP IP模型都具有的层次是 A 会话层 网络层和物理层 B 表示层 会话层和数据链路层 C 网络层 传输层和应用层 D 表示层 数据链路层和物理层 2 对于计算机网络体系结构 下列关于第N层和第N 1层的关系
  • 蓝桥杯:字符串

    题目链接 include
  • notepad++字符串替换

    删除空白行 在编辑选项里面包括很多功能 编辑 gt 行操作 gt 移除空行 包括空白字符 行首添加字符串 按CTRL F 选择替换页签 选择正则表达式 查找目标 设置为 替换为 设置自己想要替换的字符串 特殊字符需要添加 进行转义 行尾添加
  • 【MySQL】34道SQL综合练习详解(员工表、部门表、工资等级表)

    文章目录 一 34道SQL综合练习 二 测试使用的数据表 三 创建测试表的SQL语句 一 34道SQL综合练习 1 查询取得每个部门最高工资的人员信息 select e ename t from emp e join select dept
  • 如何使用PCL将XYZRGB点云转换为彩色mesh模型

    如何使用PCL将XYZRGB点云转换为彩色mesh模型 最近完成了一个使用RGBD传感器 构建物体模型的小demo 其中有点难的最后一步是如何将获得的物体点云变成彩色mesh模型 效果图如下 从点云变成彩色mesh 其实整体的步骤可以总结如
  • M1 macbook上安装docker 编译内核 并使用qemu启动内核。

    一 编译内核并通过qemu启动内核 1 在M1上安装docker这个就不用提供步骤了 网上自行搜索 2 在M1上pull一个ubuntu的容器 docker pull ubuntu 18 04 docker images REPOSITOR
  • 卡尔曼滤波及其MATLAB程序

    今天写了个卡尔曼滤波的小程序 希望对有需要的同学有点帮助 卡尔曼滤波是一个很常用的滤波算法 与维纳滤波相比有很多长处 这里我们把Kalman Filter简称为KF KF的基本思想是 采用信号 噪声 状态空间模型 利用前一时刻的状态最优估计
  • 学习python笔记01

    一 python是什么 人生苦短 我用python python是一门解释型语言 边解释边运行 与编译型语言的区别是 编译型语言是先编译后运行 python语言的特点 1 优雅 2 明确 3 简单 python是一个完全面向对象语言 具有强
  • 纯java实现相片转素描

    1 实例演示图片转素描效果 首先我们来看一下具体的效果 在项目中添加依赖
  • unity制作一个可以自由滑动收缩的历史记录功能。

    公司在做一款模拟经营类的卖车游戏 需要一个简单的历史记录功能 放在左上角 记录最近20条的收入 支出记录 超过2秒不动则收起 收起时展示最近的一个消息记录 用到的组件是ScrollView 使用方法可以参考我写过的一篇博客 ScrollVi
  • Input.GetAxis _ Unity3d

    Input GetAxis 获取轴 static function GetAxis axisName string float Description描述 Returns the value of the virtual axis iden
  • 【论文精读】时序逻辑应用之模型预测控制Model Predictive Control with Signal Temporal Logic Specifications

    前言 因为天天写代码实在是太枯燥了 所以读点其他东西来调剂一下 这样科研进度不至于停下 前面读了几篇关于时序逻辑学习的文章 今天来了解一下时序逻辑公式在控制中的应用 Raman V Donze A Maasoumy M Murray R M
  • Android Studio编译失败问题(aapt2)

    Android Studio 3 1编译时出错 org gradle api tasks TaskExecutionException Execution failed for task app mergeDebugResources at
  • 心灵鸡汤

    心灵鸡汤 比尔盖茨不想弯腰去捡100美金 浪费了1秒 时间是最宝贵 有限的时间资源最大化 如果你不够优秀 人脉是不值钱的 它不是追求来的 而是吸引来的 只有等价的交换 才能得到合理的帮助 虽然听起来很冷 但这是事实 与凤凰同飞 必是俊鸟 与
  • AESCBCUtil

    import javax crypto Cipher import javax crypto spec IvParameterSpec import javax crypto spec SecretKeySpec import org ap
  • 面试必问的 CAS ,要多了解

    前言 CAS Compare and Swap 即比较并替换 实现并发算法时常用到的一种技术 Doug lea大神在java同步器中大量使用了CAS技术 鬼斧神工的实现了多线程执行的安全性 CAS的思想很简单 三个参数 一个当前内存值V 旧