Java CAS详解
CAS介绍
CAS(Compare and swap)是并发编程中经常用到的一项技术。大体上讲,CAS算法会先使用期望值比较变量的实际值,如果相同,就使用目标值替换变量的值。如果不相同,就会重新读取期望值再次进行对比和替换。
CAS主要是为了解决共享资源被加锁后,线程从阻塞状态切换回运行态比较耗时的问题,CAS是在不阻塞线程的情况下达到资源互斥的目的。但是如果共享资源竞争比较激烈就不适合使用CAS了,CAS会导致很多线程一直在重试访问共享资源,造成CPU资源的浪费。另外,如果共享资源的操作比较耗时,也就不适合使用CAS,这种场景使用阻塞的资源锁会更合适。
CAS源码分析
以AtomicInteger.getAndIncrement为例进行源码分析,下面代码来自openJdk8源码:
1 | public class AtomicInteger extends Number implements java.io.Serializable { |
AtomicInteger.getAndIncrement
通过Unsafe.getAndAddInt
方法实现:
1 | /** Unsafe.java **/ |
getAndAddInt
函数中,首先获取到期望值v
,v + delta
是目标值。然后进入compareAndSwapInt函数,compareAndSwapInt会首先对比实际值与期望值是否相同,如果相同就替换并返回true,while循环退出。如果不相同,compareAndSwapInt就返回false,继续执行while循环读取期望值,然后再次进行对比替换的步骤。
compareAndSwapInt
是native函数,源码在hotspot的unsafe.cpp
文件中,在现在的系统中对比替换操作都是通过硬件机制来实现。
1 | UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) |
Atomic::cmpxchg
通过内嵌汇编语言来实现,在不同的硬件上有不同的实现。下面代码是windows x86平台的汇编代码:
1 | /** [hotspot]\src\os_cpu\windows_x86\vm\atomic_windows_x86.inline.hpp **/ |
代码中cmpxchg
汇编指令的含义是对比累加器(al/ax/eax/rax)和[edx]中的值([edx]表示变量地址,本质上就是一个指针),如果值相同就把ecx寄存器的值放入[edx]中,否则就会把[edx]指向的变量值加载到累加器中。
备注:AtomicInteger和Unsafe代码来之openJdk8:https://hg.openjdk.org/jdk8/jdk8/jdk。unsafe.cpp代码在openjdk8 hotspot源码中:https://hg.openjdk.org/jdk8/jdk8/hotspot
ABA问题
CAS会有ABA的问题,AtomicInteger对于getAndIncrement
这种只进行加运算的操作不会存在ABA问题。但如果还有减运算就会存在ABA问题。java也提供了AtomicStampedReference
类来解决ABA问题。
参考文章
Compare and Swap
CMPXCHG — Compare and Exchange
从汇编底层全面解析 CAS 的来龙去脉
LongAdder解析
AQS详解(面试)
并发编程的灵魂:CAS机制详解
SynchronousQueue实现原理