1. JUC 中的 CAS

1.1. 是什么


1.2. 底层原理

1.2.1. 保证可见性


1.2.2. 保证原子性

1.3. 汇编实现

1.4. synchronized 中的 CAS

1.4.1. Atomic::cmpxchg_ptr

轻量级锁加锁的时候,如果处于无锁状态,则会通过 CAS 操作将锁对象的 Mark Word 更新为指向 Lock Record 的指针,相关代码如下:

1
2
3
4
5
6
7
8
9
10
if (mark->is_neutral()) {  
// Anticipate successful CAS -- the ST of the displaced mark must
// be visible <= the ST performed by the CAS.
lock->set_displaced_header(mark);
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
return ;
}
// Fall through to inflate() ...
}

其中 CAS 操作是通过 Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark) 这段代码实现的。它有 3 个参数:

  1. 第一个参数是 exchange_value(新值)
  2. 第二个参数是 dest(目标地址)
  3. 第三个参数是 compare_value(原值)

它的含义是:

  1. 如果目标地址的值是原值,即 dest==compare_value,则将其更新为新值,并且返回 compare_value(原值)
  2. 否则,不做更新,并且返回第一个入参,exchange_value(新值)
  3. 因此,当返回结果是 compare_value(原值) 时,则说明更新成功

虽然函数最后返回的是第一个入参 exchange_value(新值),但是返回之前,汇编底层将第二个入参 dest 的值赋值给了第一个参数 exchange_value,所以最终返回的这个实际原值就是 dest 指向的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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:

inline jint Atomic::cmpxchg(jint exchange_value, volatile jint* dest, jint compare_value) {
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest // 将dest保存到edx寄存器
mov ecx, exchange_value // 将exchange_value保存到ecx寄存器
mov eax, compare_value // 将compare_value保存到ecx寄存器
LOCK_IF_MP(mp) // lock
cmpxchg dword ptr [edx], ecx // 交换
}
}

1.4.2. 返回值

1.4.2.1. 返回第一个参数

1、
https://gorden5566.com/post/1055.html#waline
 
2、
https://blog.csdn.net/aileitianshi/article/details/108844586

1.4.2.2. 返回第二个参数✅

1、
https://juejin.cn/post/7104638789456232478

2、
https://cgiirw.github.io/2018/10/14/Blocked02/

1.4.2.3. 实际原值

1、
https://www.javazhiyin.com/24364.html

2、
https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/

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;
}
1
2
3
4
5
%0"=a" (exchange_value) // 第1个输出参数。cmpxchgl指令执行结束后,将寄存器eax的值保存到exchange_value,在方法执行结束后返回
%1"r" (exchange_value) // 第1个输入参数。编译器会选择任意一个可用的寄存器来存储exchange_value,例如使用寄存器ecx。
%2"a" (compare_value) // 第2个输入参数。使用寄存器eax来存储待比较的值compare_value,执行cmpxchgl指令时会用到寄存器eax。
%3"r" (dest) // 第3个输入参数。编译器会选择任意一个可用的寄存器来存储目标地址dest,例如使用寄存器edx。
%4"r" (mp) // 第4个输入参数。编译器会选择任意一个可用的寄存器来存储多处理器标志

2. 面试题

2.1. 底层原理

CompareAndSwap 是一个 native 方法,实际上它最终还是会面临同样的问题,就是 先从内存地址中读取 state 的值,然后去比较,最后再修改。 这个过程不管是在什么层面上实现,都会存在原子性问题。 所以呢,CompareAndSwap 的底层实现中,在多核 CPU 环境下,会增加一个 Lock 指令对缓存或者总线加锁,从而保证比较并替换这两个指令的原子性。

2.2. 应用场景

  1. 第一个是 J.U.C 里面 Atomic 的原子实现,比如 AtomicInteger,AtomicLong。
  2. 第二个是实现多线程对共享资源竞争的互斥性质,比如在 AQS、 ConcurrentHashMap、ConcurrentLinkedQueue 等都有用到。

image.png

3. 参考与感谢

[[../../../../cubox/006-ChromeCapture/CAS 原理和缺陷 huzb的博客]]
[[../../../../cubox/006-ChromeCapture/探底分析Java原子类CAS的实现原理—从HotSpot源码到CPU指令cmpxchg HeapDump性能社区]]