JAVA CAS底层实现
2020-04-07 本文已影响0人
老荀
JAVA CAS
Java CAS底层都是调用了Unsafe类的compareAndSwap方法
都是native的方法
package sun.misc;
...
public final class Unsafe {
...
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
...
}
然后源码都是c或者c++实现了
// 源码路径 openjdk/hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapObject(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jobject e_h, jobject x_h))
UnsafeWrapper("Unsafe_CompareAndSwapObject");
oop x = JNIHandles::resolve(x_h);
oop e = JNIHandles::resolve(e_h);
oop p = JNIHandles::resolve(obj);
HeapWord* addr = (HeapWord *)index_oop_from_field_offset_long(p, offset);
oop res = oopDesc::atomic_compare_exchange_oop(x, addr, e, true);
jboolean success = (res == e);
if (success)
update_barrier_set((void*)addr, x);
return success;
UNSAFE_END
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
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong e, jlong x))
UnsafeWrapper("Unsafe_CompareAndSwapLong");
Handle p (THREAD, JNIHandles::resolve(obj));
jlong* addr = (jlong*)(index_oop_from_field_offset_long(p(), offset));
if (VM_Version::supports_cx8())
return (jlong)(Atomic::cmpxchg(x, addr, e)) == e;
else {
jboolean success = false;
ObjectLocker ol(p, THREAD);
if (*addr == e) { *addr = x; success = true; }
return success;
}
UNSAFE_END
#include "runtime/atomic.inline.hpp"
// 源码路径 hotspot/src/share/vm/runtime/atomic.cpp
jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {
assert(sizeof(jbyte) == 1, "assumption.");
uintptr_t dest_addr = (uintptr_t)dest;
uintptr_t offset = dest_addr % sizeof(jint);
volatile jint* dest_int = (volatile jint*)(dest_addr - offset);
jint cur = *dest_int;
jbyte* cur_as_bytes = (jbyte*)(&cur);
jint new_val = cur;
jbyte* new_val_as_bytes = (jbyte*)(&new_val);
new_val_as_bytes[offset] = exchange_value;
while (cur_as_bytes[offset] == compare_value) {
jint res = cmpxchg(new_val, dest_int, cur);
if (res == cur) break;
cur = res;
new_val = cur;
new_val_as_bytes[offset] = exchange_value;
}
return cur_as_bytes[offset];
}
unsigned Atomic::cmpxchg(unsigned int exchange_value,
volatile unsigned int* dest, unsigned int compare_value) {
assert(sizeof(unsigned int) == sizeof(jint), "more work to do");
return (unsigned int)Atomic::cmpxchg((jint)exchange_value, (volatile jint*)dest,
(jint)compare_value);
}
// 源码路径 hotspot/src/share/vm/runtime/atomic.inline.hpp
// 不同的操作系统不同架构,加载的文件就会不同
#ifndef SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP
#define SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP
#include "runtime/atomic.hpp"
// Linux
#ifdef TARGET_OS_ARCH_linux_x86
# include "atomic_linux_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_sparc
# include "atomic_linux_sparc.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_zero
# include "atomic_linux_zero.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_arm
# include "atomic_linux_arm.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_ppc
# include "atomic_linux_ppc.inline.hpp"
#endif
// Solaris
#ifdef TARGET_OS_ARCH_solaris_x86
# include "atomic_solaris_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_solaris_sparc
# include "atomic_solaris_sparc.inline.hpp"
#endif
// Windows
#ifdef TARGET_OS_ARCH_windows_x86
# include "atomic_windows_x86.inline.hpp"
#endif
// BSD
#ifdef TARGET_OS_ARCH_bsd_x86
# include "atomic_bsd_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_bsd_zero
# include "atomic_bsd_zero.inline.hpp"
#endif
#endif // SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP
我们这里就看看BSD和Linux的系统
// BSD
// 源码路径 hotspot/src/os_cpu/bsd_x86/vm/atomic_bsd_x86.inline.hpp
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;
}
inline jlong Atomic::cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value) {
bool mp = os::is_MP();
__asm__ __volatile__ (LOCK_IF_MP(%4) "cmpxchgq %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
// Linux
// 源码路径 hotspot/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp
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;
}
inline jlong Atomic::cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value) {
bool mp = os::is_MP();
__asm__ __volatile__ (LOCK_IF_MP(%4) "cmpxchgq %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
比较下来这两个不同操作系统的是一样的
image.png
既然都看了就顺便再看下windows,可以看到windows的大不一样了
// Windows
// 源码路径 hotspot/src/os_cpu/windows_x86/vm/atomic_windows_x86.inline.hpp
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
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
inline jlong Atomic::cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value) {
int mp = os::is_MP();
jint ex_lo = (jint)exchange_value;
jint ex_hi = *( ((jint*)&exchange_value) + 1 );
jint cmp_lo = (jint)compare_value;
jint cmp_hi = *( ((jint*)&compare_value) + 1 );
__asm {
push ebx
push edi
mov eax, cmp_lo
mov edx, cmp_hi
mov edi, dest
mov ebx, ex_lo
mov ecx, ex_hi
LOCK_IF_MP(mp)
cmpxchg8b qword ptr [edi]
pop edi
pop ebx
}
}
上面的__asm__和__asm都是内联了汇编语言的写法
首先LOCK_IF_MP是定义在上面的宏用来判断当前运行环境是不是多核的,多核的就要加锁
// Linux, BSD
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
// Windows
标记处
#define LOCK_IF_MP(mp) __asm cmp mp, 0 \
// 如果 mp = 0,表明是线程运行在单核 CPU 环境下。此时 je 会跳转到 L0
__asm je L0 \
// 0xF0 是 lock 前缀的机器码,多核的情况下会多执行emit这个指令
__asm _emit 0xF0 \
__asm L0:
至于为什么要用硬编码,参考了知乎的回答
如下:
- 首先要明确是一个事实,就是编译器并不会随着cpu的更新而即时更新,这就有可能出现cpu支持了某种指令,但是编译器不支持,然而你还想用这个指令,此时就可以用_emit来硬编码
- 编译器不支持__asm lock __asm L0:__asm cmpxchg dword ptr [edx], ecx这种直接把lock单独放在一行的语法, 所以可以用_emit 来达到通过编译的效果
内联汇编语言的语法
asm volatile("Instruction List"
: Output
: Input
: Clobber/Modify);
序号占位符就是前面描述的%0、%1、%2、%3、%4、%5、%6、%7、%8、%9
dword 由 4 字节长(32 位整数)的数字表示的数据
qword 由 8 字节长(32 位整数)的数字表示的数据
ptr 是pointer的简写,代表指针,当两个操作数的宽度不一样时,就要用到ptr
参考
https://www.cnblogs.com/noKing/p/9094983.html
https://blog.csdn.net/shakesky/article/details/6343395
https://www.zhihu.com/question/50878124/answer/123099923