JAVA CAS底层实现

作者: 老荀 | 来源:发表于2020-04-07 11:38 被阅读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

    相关文章

      网友评论

        本文标题:JAVA CAS底层实现

        本文链接:https://www.haomeiwen.com/subject/pglgphtx.html