• JDK 1.8 sun.misc.Unsafe类CAS底层实现


    在java.util.concurrent包下面的很多类为了追求性能都采用了sun.misc.Unsafe类中的CAS操作,从而避免使用synchronized等加锁方式带来性能上的不足。

    在sun.misc.Unsafe中CAS方法如下:

    1     public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
    2 
    3     public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    4 
    5     public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

    在JDK1.8中只有上述三个CAS方法,其方法参数含义为:var1为待修改的field对象;var2为field对象偏移量,为long型;var4为期望值;var5或var6为替换值,当var1[offset] == var4则设置var1[offset] = var5(var6)。

    这三个方法都是native方法,可以查看hotspot源码查看其底层实现:(hotspot/src/share/vm/prims/unsafe.cpp)

    1 #define FN_PTR(f) CAST_FROM_FN_PTR(void*, &f)
    2 
    3 {CC"compareAndSwapObject", CC"("OBJ"J"OBJ""OBJ")Z",  FN_PTR(Unsafe_CompareAndSwapObject)},
    4 {CC"compareAndSwapInt",  CC"("OBJ"J""I""I"")Z",      FN_PTR(Unsafe_CompareAndSwapInt)},
    5 {CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z",      FN_PTR(Unsafe_CompareAndSwapLong)},
     1 UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapObject(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jobject e_h, jobject x_h))
     2   UnsafeWrapper("Unsafe_CompareAndSwapObject");
     3   oop x = JNIHandles::resolve(x_h); // 更新值
     4   oop e = JNIHandles::resolve(e_h); // 期望值
     5   oop p = JNIHandles::resolve(obj); // 更新对象
     6   HeapWord* addr = (HeapWord *)index_oop_from_field_offset_long(p, offset); // 根据偏移量offset获取内存中的具体位置
     7   oop res = oopDesc::atomic_compare_exchange_oop(x, addr, e, true); // 调用方法执行CAS操作
     8   jboolean success  = (res == e);  // 如果返回值res==e则表明满足compare条件,swap成功
     9   if (success)
    10     update_barrier_set((void*)addr, x); // 更新memory barrier
    11   return success;
    12 UNSAFE_END
    13 
    14 UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
    15   UnsafeWrapper("Unsafe_CompareAndSwapInt");
    16   oop p = JNIHandles::resolve(obj);
    17   jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
    18   return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
    19 UNSAFE_END
    20 
    21 UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong e, jlong x))
    22   UnsafeWrapper("Unsafe_CompareAndSwapLong");
    23   Handle p (THREAD, JNIHandles::resolve(obj));
    24   jlong* addr = (jlong*)(index_oop_from_field_offset_long(p(), offset));
    25   if (VM_Version::supports_cx8())
    26     return (jlong)(Atomic::cmpxchg(x, addr, e)) == e;
    27   else {
    28     jboolean success = false;
    29     ObjectLocker ol(p, THREAD);
    30     if (*addr == e) { *addr = x; success = true; }
    31     return success;
    32   }
    33 UNSAFE_END

    先来看下Unsafe_CompareAndSwapObject方法,该方法通过调用index_oop_from_field_offset_long方法找到需要执行CAS对象的具体地址,然后调用atomic_compare_exchange_oop方法执行CAS操作。

    继续深入atomic_compare_exchange_oop方法看一下,源码如下

     1 // 声明在hotspot/src/share/vm/oops/oop.hpp
     2 static oop atomic_compare_exchange_oop(oop exchange_value,
     3                                        volatile HeapWord *dest,
     4                                        oop compare_value,
     5                                        bool prebarrier = false);
     6 
     7 // 定义在hotspot/src/share/vm/oops/oop.inline.hpp
     8 inline oop oopDesc::atomic_compare_exchange_oop(oop exchange_value,
     9                                                 volatile HeapWord *dest,
    10                                                 oop compare_value,
    11                                                 bool prebarrier) {
    12   if (UseCompressedOops) {
    13     if (prebarrier) {
    14       update_barrier_set_pre((narrowOop*)dest, exchange_value);
    15     }
    16     // encode exchange and compare value from oop to T
    17     narrowOop val = encode_heap_oop(exchange_value);
    18     narrowOop cmp = encode_heap_oop(compare_value);
    19 
    20     narrowOop old = (narrowOop) Atomic::cmpxchg(val, (narrowOop*)dest, cmp);
    21     // decode old from T to oop
    22     return decode_heap_oop(old);
    23   } else {
    24     if (prebarrier) {
    25       update_barrier_set_pre((oop*)dest, exchange_value);
    26     }
    27     return (oop)Atomic::cmpxchg_ptr(exchange_value, (oop*)dest, compare_value);
    28   }
    29 }

    在atomic_compare_exchange_oop方法中,核心的CAS操作最终是调用了Atomic::cmpxchg(val, (narrowOop*)dest, cmp)函数或者Atomic::cmpxchg_ptr(exchange_value, (oop*)dest, compare_value)函数。

     Atomic::cmpxchg(val, (narrowOop*)dest, cmp)函数虽然有很多重载函数,但最终都是调用的下面的函数:

     1 // hotspot/src/share/vm/runtime/Atomic.cpp
     2 jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {
     3   assert(sizeof(jbyte) == 1, "assumption.");
     4   uintptr_t dest_addr = (uintptr_t)dest;
     5   uintptr_t offset = dest_addr % sizeof(jint);
     6   volatile jint* dest_int = (volatile jint*)(dest_addr - offset);
     7   jint cur = *dest_int; // 对象当前值
     8   jbyte* cur_as_bytes = (jbyte*)(&cur);  // 当前值cur的地址
     9   jint new_val = cur;
    10   jbyte* new_val_as_bytes = (jbyte*)(&new_val);  // new_val地址
    11   // new_val存exchange_value,后面修改则直接从new_val中取值
    12   new_val_as_bytes[offset] = exchange_value;
    13   // 比较当前值与期望值,如果相同则更新,不同则直接返回
    14   while (cur_as_bytes[offset] == compare_value) {
    15     // 调用汇编指令cmpxchg执行CAS操作,期望值为cur,更新值为new_val
    16     jint res = cmpxchg(new_val, dest_int, cur);
    17     if (res == cur) break;
    18     cur = res;
    19     new_val = cur;
    20     new_val_as_bytes[offset] = exchange_value;
    21   }
    22   // 返回当前值
    23   return cur_as_bytes[offset];
    24 }

    Atomic::cmpxchg_ptr(exchange_value, (oop*)dest, compare_value)函数在不同系统中都有各自的声明,但是最终都是调用的下面的函数:

     1 // hotspot/src/os_cpu/solaris_x86/vm/Atomic_solaris_x86.inline.hpp
     2 
     3   // This is the interface to the atomic instruction in solaris_i486.s.
     4   jlong _Atomic_cmpxchg_long_gcc(jlong exchange_value, volatile jlong* dest, jlong compare_value, int mp);
     5 
     6   inline jlong _Atomic_cmpxchg_long(jlong exchange_value, volatile jlong* dest, jlong compare_value, int mp) {
     7 #ifdef AMD64
     8     __asm__ __volatile__ (LOCK_IF_MP(%4) "cmpxchgq %1,(%3)"
     9                         : "=a" (exchange_value)
    10                         : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
    11                         : "cc", "memory");
    12     return exchange_value;
    13 #else
    14     return _Atomic_cmpxchg_long_gcc(exchange_value, dest, compare_value, os::is_MP());
    15 
    16     #if 0
    17     // The code below does not work presumably because of the bug in gcc
    18     // The error message says:
    19     //   can't find a register in class BREG while reloading asm
    20     // However I want to save this code and later replace _Atomic_cmpxchg_long_gcc
    21     // with such inline asm code:
    22 
    23     volatile jlong_accessor evl, cvl, rv;
    24     evl.long_value = exchange_value;
    25     cvl.long_value = compare_value;
    26     int mp = os::is_MP();
    27 
    28     __asm__ volatile ("cmp $0, %%esi
    	"
    29        "je 1f 
    	"
    30        "lock
    	"
    31        "1: cmpxchg8b (%%edi)
    	"
    32        : "=a"(cvl.words[0]),   "=d"(cvl.words[1])
    33        : "a"(cvl.words[0]), "d"(cvl.words[1]),
    34          "b"(evl.words[0]), "c"(evl.words[1]),
    35          "D"(dest), "S"(mp)
    36        :  "cc", "memory");
    37     return cvl.long_value;
    38     #endif // if 0
    39 #endif // AMD64
    40   }

    在这个方法中废弃了32位系统的cmpxchg8b指令实现CAS操作方式,只提供了AMD64位操作系统cmpxchgq指令实现方式。

    从上面可以看出无论是Atomic::cmpxchg(val, (narrowOop*)dest, cmp)函数或者Atomic::cmpxchg_ptr(exchange_value, (oop*)dest, compare_value)函数,二者最终都是通过一条汇编指令实现CAS操作的。

    Unsafe_CompareAndSwapInt和Unsafe_CompareAndSwapLong两个方法都是调用Atomic::cmpxchg(val, (narrowOop*)dest, cmp)函数实现的,这个函数上面已经解释过。

    综合上面的源码分析,可以知道sun.misc.Unsafe类中的CAS都是通过一条汇编指令实现的,这也就不难理解为什么这个操作可以保证原子性了。

    参考文章:

    http://blog.csdn.net/qqqqq1993qqqqq/article/details/75211993

    https://www.cnblogs.com/dennyzhangdd/p/6734933.html

  • 相关阅读:
    ubuntu下如何安装hg(mercurial)?
    vi启动时报错:YouCompleteMe unavailable: requires Vim 7.4.1578+如何处理?
    linux中如何配置vim的别名为vi?
    linux shell中如何让$就表示为$呢?
    redhat 7.6下如何更新YUM源(仓库)?
    redhat下如何查看red hat版本号?
    javascript快速入门11--正则表达式
    javascript快速入门10--运算符,语句
    javascript快速入门9--引用类型
    javascript快速入门7--ECMAScript语法基础
  • 原文地址:https://www.cnblogs.com/snowater/p/8303698.html
Copyright © 2020-2023  润新知