jdk里的CAS操作实现

在看轻量级锁加锁源码时,顺便看了下 jdk 里的 CAS 操作实现,本文记录下相关代码。

轻量级锁加锁的时候,如果处于无锁状态,则会通过 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(原值)

它的含义是:如果目标地址的值是原值,则将其更新为新值,并且返回原值。否则,不做更新,并且返回新值。因此,当返回结果是原值时,则说明更新成功

Atomic::cmpxchg_ptr 的实现与硬件相关,因此对应会有多个实现

image-20200611012350383

以 linux x86 为例,它的 int 类型的 CAS 实现如下:

1
2
3
inline void* Atomic::cmpxchg_ptr(void* exchange_value, volatile void* dest, void* compare_value) {
return (void*)cmpxchg((jint)exchange_value, (volatile jint*)dest, (jint)compare_value);
}

它是调用的 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;
}

先看第一行 int mp = os::is_MP(),它的实现如下:

1
2
3
4
5
6
7
8
9
10
11
// Interface for detecting multiprocessor system
static inline bool is_MP() {
// During bootstrap if _processor_count is not yet initialized
// we claim to be MP as that is safest. If any platform has a
// stub generator that might be triggered in this phase and for
// which being declared MP when in fact not, is a problem - then
// the bootstrap routine for the stub generator needs to check
// the processor count directly and leave the bootstrap routine
// in place until called after initialization has ocurred.
return (_processor_count != 1) || AssumeMP;
}

这是判断是否为多处理器,如果是多处理器则返回 true。判断结果会最终生成指令的参数

再看下 LOCK_IF_MP(%4) 的实现:

1
2
// Adding a lock prefix to an instruction on MP machine
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

它的作用是针对多核处理器指令添加 lock 前缀。最后看下如下关键代码:

1
2
3
4
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");

这里 asm 表示后面的是汇编指令,volatile 表示编译器不需要优化代码,括号中间则是汇编代码。简单来看,单核处理器使用 cmpxchgl 命令实现 CAS 操作,多核处理器使用带 lock 前缀的 cmpxchgl 命令实现 CAS 操作。

相对来说,windows_x86 的实现可能看起来更直观些

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 // 交换
}
}

同样也需要判断是否为多核处理器,只是寄存器操作更直观