生活资讯
java 中cas 是怎么实现的 CAS如何保证原子性
2023-04-19 13:32  浏览:54

前文《Java面试必考问题:如何理解volatile关键字? 》介绍了 volatile 关键字,可以保证多线程的可见性和有序性,但是不能保证原子性。要保证多线程操作的原子性,除了可以使用 synchronized 关键字,另外还可以通过CAS算法。

在JDK1.5以后的 java.util.concurrent.atomic 包(JUC)下,提供了大量的原子变量类型,包括AtomicBoolean,AtomicInteger,AtomicLong等,其内部都是使用CAS算法。

什么是CAS

CAS(Compare-and-Swap),是一种系统原语,是不可分割的操作系统指令。

CAS包含了3个操作数:

  • 要操作的内存值 V
  • 旧的预期值 A
  • 要修改的新值 B

java 中cas 是怎么实现的 CAS如何保证原子性(1)

CAS算法流程

当且仅当 V == A 时, 内存值更新为新值 B; 否则,不会执行任何操作。CAS更新不成功,则开始自旋,重复刚才的过程。

在整个过程中,是不需要在软件层面加锁的,这是一种乐观锁的策略。只要内存值没有发生变化,那么相信在这次修改之前,没有其它线程去动过它。synchronized锁是悲观锁策略。

CAS的底层实现

底层硬件通过将 CAS 里的多个操作在处理器指令层面上实现,通过一条处理器指令保证了原子性操作,现代多核处理器支持的原子性操作中通常都包括CAS功能。下图是CAS操作的函数调用链,我们看一下Java的CAS原子操作是如何在处理器指令层面实现的。

在IA64,x86 指令集中有 cmpxchg 指令完成 CAS 功能,在 sparc-TSO 也有 casa 指令实现,而在 ARM 和 PowerPC 架构下,则需要使用 ldrex/strex 一对指令来完成 LL/SC 的功能。

java 中cas 是怎么实现的 CAS如何保证原子性(2)

JDK1.8中AtomicInteger的函数调用链

以JDK1.8的 AtomicInteger 为例,内部值保存在私有成员变量 value 中,compareAndSet() 方法用于CAS,原子方式更新 value 的值,这个方法调用了 sun.misc.Unsafe 类的 compareAndSwapInt() 方法。

java 中cas 是怎么实现的 CAS如何保证原子性(3)

AtomicInteger 源代码节选

Java代码不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类是底层的CAS操作类,compareAndSwapInt() 也是一个native方法,具体的实现在Hotspot源代码的 /src/share/vm/prims/unsafe.cpp 文件中。其中调用了 Atomic::cmpxchg 函数(C ),实现字段的CAS操作。

java 中cas 是怎么实现的 CAS如何保证原子性(4)

Unsafe.compareAndSwapInt()的native实现

对于linux_x86平台,Atomic::cmpxchg 对应的源代码在:hotspotsrcos_cpulinux_x86vmatomic_linux_x86.inline.hpp。Atomic::cmpxchg 函数的代码中包含了汇编指令 cmpxchgl。

cmpxchgl指令的作用:

  • 比较寄存器 eax 和目标操作数 edx 对应的内存单元的值是否相等。
  • 如果相等,则将寄存器 ecx 的值保存到操作数 edx 对应的内存单元中,替换掉旧值。
  • 如果不相等,则将 edx 对应内存单元的值加载到寄存器 eax 中。

java 中cas 是怎么实现的 CAS如何保证原子性(5)

Atomic::cmpxchg()函数:LOCK_IF_MP宏在cmpxchg加了lock前缀

当在多处理器上执行 cmpxchg 指令时,将会在该指令的前面添加一条 lock 前缀指令,是通过 LOCK_IF_MP 宏实现的。因为 cmpxchg 指令本身并不是一个原子操作,因此需要通过 lock 前缀指令加锁来让指令实现原子操作。

对于windows_x86平台,Atomic::cmpxchg 对应的源代码在:hotspotsrcos_cpuwindows_x86vmatomic_windows_x86.inline.hpp。由于平台不同,在代码的写法上存在一些差异。实现原理与Linux是相同的,这里不再赘述。

对于以上过程,我们不必深究,重点是明白Java中的CAS是利用CPU调用底层指令实现的。

与synchronized的对比

在线程冲突较轻的情况下,使用synchronized锁会有较大的系统开销(线程需要在用户态与内核态之间切换);CAS基于硬件实现,不需要进入内核,因此可以获得更高的性能。

在线程冲突严重的情况下,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

我会持续更新关于物联网、云原生以及数字科技方面的文章,用简单的语言描述复杂的技术,也会偶尔发表一下对IT产业的看法,欢迎大家关注,谢谢。

,
发表评论
0评