JVM的CAS实现原理解析

来源:互联网 发布:linux修改host 编辑:程序博客网 时间:2024/05/06 23:20

        目前JAVA除了使用synchronized关键字来控制进行共享数据的同步外,还引入了轻量级并发框架,以使得JAVA可以以优雅的面向对象的方式对共享数据进行同步,该并发框架在集合等框架中被广泛使用,而轻量级并发的实现原理就是CAS,所以深入理解CAS的实现,攻克轻量级并发这个山头堡垒,对于正确使用JAVA并发框架非常重要,所以本文蜻蜓点水大概了解下CAS的实现,不详细分析每一行代码,主要这种分析CAS实现的大概细节,有关指针,汇编等的细节,还请参考相关书籍,不正确地方指出,还望指出。

一 、 CAS的JNI JAVA入口类概述

在sun.misc包中,有这样一个类,Unsafe.java,这是java调用轻量级锁的入口,这里面大部分方法被声明为native方法,表明这里的功能大部分依赖底层的平台特性实现,该类位于

openjdk/jdk/src/share/classes/sun/misc/目录下,部分代码片段如下:

package sun.misc;import java.security.*;import java.lang.reflect.*;import sun.reflect.CallerSensitive;import sun.reflect.Reflection;/** * A collection of methods for performing low-level, unsafe operations. * Although the class and all methods are public, use of this class is * limited because only trusted code can obtain instances of it. * * @author John R. Rose * @see #getUnsafe */public final class Unsafe {    private static native void registerNatives();    static {        registerNatives();        sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");    }    private Unsafe() {}    private static final Unsafe theUnsafe = new Unsafe();       @CallerSensitive    public static Unsafe getUnsafe() {        Class cc = Reflection.getCallerClass();        if (cc.getClassLoader() != null)            throw new SecurityException("Unsafe");        return theUnsafe;    }       /**     * Atomically update Java variable to <tt>x</tt> if it is currently     * holding <tt>expected</tt>.     * @return <tt>true</tt> if successful     */    public final native boolean compareAndSwapObject(Object o, long offset,                                                     Object expected,                                                     Object x); // 其他代码忽略}

从这个类的构造函数可看出,这个类不能被外部使用new 操作符创建对象,只能通过调用静态公有方法getUnsafe获取theUnsafe对象,而且普通的java类中调用Unsafe.getUnsafe()也不被允许,只能通过反射获取,如下:

 public static Unsafe getUnsafeInstance() throws Exception {        Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");        theUnsafeField.setAccessible(true);        return (Unsafe) theUnsafeField.get(Unsafe.class);    }

这个类中的方法几乎都是native声明的,比如compareAndSwapObject

 /**     * Atomically update Java variable to <tt>x</tt> if it is currently     * holding <tt>expected</tt>.     * @return <tt>true</tt> if successful     */    public final native boolean compareAndSwapObject(Object o, long offset,                                                     Object expected,                                                     Object x);

参数说明:

o:需要操作字段的对象

expected:期望的对象

x:需要交换的值

offset:需要操作的对象相对于o对象的地址偏移量,通过调用Unsafe.objectFieldOffset(Field field)获取,比如ConcurrentHashMap.HashEntry对next字段偏移量的获取

static final class HashEntry<K,V> {        // 其他代码忽略        volatile HashEntry<K,V> next;        // Unsafe mechanics        static final sun.misc.Unsafe UNSAFE;        static final long nextOffset;        static {            try {                UNSAFE = sun.misc.Unsafe.getUnsafe();                Class k = HashEntry.class;                nextOffset = UNSAFE.objectFieldOffset                    (k.getDeclaredField("next"));            } catch (Exception e) {                throw new Error(e);            }        }    }

调用结果:比较对象o的是否是期待的对象expected,如果是,那么o和x交换,后续的篇幅都是以该方法为例讲解。


二、JNI的具体实现(unsafe.cpp)

jvm底层的代码都使用C++实现,轻量级锁也是如此,unsafe.cpp位于hostspot/src/share/vm/prims下,部分代码片段如下:

#define UNSAFE_ENTRY(result_type, header) \  JVM_ENTRY(result_type, header)#define UNSAFE_END JVM_END// JSR166 ------------------------------------------------------------------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);  if (UseCompressedOops) {    update_barrier_set_pre((narrowOop*)addr, e);  } else {    update_barrier_set_pre((oop*)addr, e);  }  oop res = oopDesc::atomic_compare_exchange_oop(x, addr, e);  jboolean success  = (res == e);  if (success)    update_barrier_set((void*)addr, x);  return success;UNSAFE_END

代码中,UNSAFE_ENTRY是JVM_ENTRY的别名,代码的最上面有定义。

上面代码分析如下,

1 对象解析

将java的对象解析为JVM的oop(ordinary object pointer,普通对象指针,而不是面向对象编程),解析方法为jniHandles.hpp(hostspot/src/share/vm/runtime/)文件中,

代码如下:

class JNIHandles : AllStatic {                    public:  // Resolve handle into oop  inline static oop resolve(jobject handle);};inline oop JNIHandles::resolve(jobject handle) {  oop result = (handle == NULL ? (oop)NULL : *(oop*)handle);  assert(result != NULL || (handle == NULL || !CheckJNICalls || is_weak_global_handle(handle)), "Invalid value read from jni handle");  assert(result != badJNIHandle, "Pointing to zapped jni handle area");  return result;};
为提高调用效率,方法被声明为内联方法,该方法就是将java对象,转换为JVM的oop

2 地址转换

将对象o转换为堆地址,通过对象o和地址偏移量确定,通过调用方法index_oop_from_field_offset_long:

inline void* index_oop_from_field_offset_long(oop p, jlong field_offset) {  jlong byte_offset = field_offset_to_byte_offset(field_offset);  // Don't allow unsafe to be used to read or write the header word of oops  assert(p == NULL || field_offset >= oopDesc::header_size(), "offset must be outside of header");#ifdef ASSERT  if (p != NULL) {    assert(byte_offset >= 0 && byte_offset <= (jlong)MAX_OBJECT_SIZE, "sane offset");    if (byte_offset == (jint)byte_offset) {      void* ptr_plus_disp = (address)p + byte_offset;      assert((void*)p->obj_field_addr<oop>((jint)byte_offset) == ptr_plus_disp,             "raw [ptr+disp] must be consistent with oop::field_base");    }    jlong p_size = HeapWordSize * (jlong)(p->size());    assert(byte_offset < p_size, err_msg("Unsafe access: offset " INT64_FORMAT " > object's size " INT64_FORMAT, byte_offset, p_size));  }#endif  if (sizeof(char*) == sizeof(jint))    // (this constant folds!)    return (address)p + (jint) byte_offset;  else    return (address)p +        byte_offset;}
从方法可看出,需要操作的对象的地址为指针p+偏移量,然后强制转换为HeapWord指针,因为方法index_oop_from_field_offset_long
返回的是void*指针,没有长度。

3 调用平台相关的原子类委托

oopDesc::atomic_compare_exchange_oop(x, addr, e)方法交换对象,该方法在oop.hpp中进行声明

class oopDesc {// 其他代码忽略public:static oop atomic_compare_exchange_oop(oop exchange_value,                                         volatile HeapWord *dest,                                         oop compare_value);}
然后在oop.inline.cpp(hostspot/src/share/vm/oops)中提供实现

inline oop oopDesc::atomic_compare_exchange_oop(oop exchange_value,                                                volatile HeapWord *dest,                                                oop compare_value) {  if (UseCompressedOops) {    // encode exchange and compare value from oop to T    narrowOop val = encode_heap_oop(exchange_value);    narrowOop cmp = encode_heap_oop(compare_value);    narrowOop old = (narrowOop) Atomic::cmpxchg(val, (narrowOop*)dest, cmp);    // decode old from T to oop    return decode_heap_oop(old);  } else {    return (oop)Atomic::cmpxchg_ptr(exchange_value, (oop*)dest, compare_value);  }}

显然,最终的代码实现委托给了Atomic::cmpxchg或Atomic::cmpxchg_ptr,所以最终的实现由Atomic类确定

三、JVM原子类的实现

1 原子方法声明

Atomic.hpp(hostspot/src/share/vm/runtime/)中代码如下:

#ifndef SHARE_VM_RUNTIME_ATOMIC_HPP#define SHARE_VM_RUNTIME_ATOMIC_HPP#include "memory/allocation.hpp"class Atomic : AllStatic { public:  // Performs atomic exchange of *dest with exchange_value.  Returns old prior value of *dest.  static jint         xchg(jint     exchange_value, volatile jint*     dest);  static unsigned int xchg(unsigned int exchange_value,                           volatile unsigned int* dest);  static intptr_t xchg_ptr(intptr_t exchange_value, volatile intptr_t* dest);  static void*    xchg_ptr(void*    exchange_value, volatile void*   dest);  // Performs atomic compare of *dest and compare_value, and exchanges *dest with exchange_value  // if the comparison succeeded.  Returns prior value of *dest.  Guarantees a two-way memory  // barrier across the cmpxchg.  I.e., it's really a 'fence_cmpxchg_acquire'.  static jbyte    cmpxchg    (jbyte    exchange_value, volatile jbyte*    dest, jbyte    compare_value);  static jint     cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value);  // See comment above about using jlong atomics on 32-bit platforms  static jlong    cmpxchg    (jlong    exchange_value, volatile jlong*    dest, jlong    compare_value);  static unsigned int cmpxchg(unsigned int exchange_value,                              volatile unsigned int* dest,                              unsigned int compare_value);  static intptr_t cmpxchg_ptr(intptr_t exchange_value, volatile intptr_t* dest, intptr_t compare_value);  static void*    cmpxchg_ptr(void*    exchange_value, volatile void*     dest, void*    compare_value);};#endif // SHARE_VM_RUNTIME_ATOMIC_HPP

2 各个平台的实现

 2.1 WINDOWS实现

各种实现的文件都位于hostspot/src/os_cpu/中,winidows的则为:atomic_windows_x86.inline.hpp(hostspot/src/os_cpu/windows_x86/vm/),其中的两个方法实现代码如下,本质是通过CAS指令,通过嵌入汇编语言实现:

// 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:#ifdef AMD64inline jint     Atomic::xchg(jint     exchange_value, volatile jint*     dest) {  // alternative for InterlockedExchange  __asm {    mov eax, exchange_value;    mov ecx, dest;    xchg eax, dword ptr [ecx];  }}inline intptr_t Atomic::xchg_ptr(intptr_t exchange_value, volatile intptr_t* dest) {  return (intptr_t)xchg((jint)exchange_value, (volatile jint*)dest);}inline void*    Atomic::xchg_ptr(void*    exchange_value, volatile void*     dest) {  return (void*)xchg((jint)exchange_value, (volatile jint*)dest);}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  }}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);}


以下面方法为例进行分析(__asm表示嵌入汇编代码):

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 //表示把目标地址放入edx寄存器中    mov ecx, exchange_value //把exchange_value放入寄存器ecx中    mov eax, compare_value // 需要和目标之地址指向的值进行比较的值放eax寄存器中    LOCK_IF_MP(mp)    cmpxchg dword ptr [edx], ecx // 执行比较操作并交换,这是一个原子操作  }}


       其中,dword ptr [edx]表示取目标地址的指向的值,由于edx的值只是一个指针,指针指向的存储单元的值和ecx宽度可能不同,所以需要明确指出dword
上述汇编代码执行后,dest指向的内存中的值为exchange_value。

2.2 LINUX实现

代码位于atomic_linux_x86.inline.hpp(hostspot/src/os_cpu/linux_x86/vm/)中,其中一个方法如下:
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;}

 其中,__asm__ __volatile__表示linux下内嵌汇编(也兼容ANSIC),并且使用关键字禁止编译器指令重排序优化,而dest使用volatile修饰(禁止编译器优化存取,比如,为了存取速度,将dest的内容存储在寄存器中,当外部程序修改dest指向的内存单元时,寄存器感知不到,还是原来的值,从而导致程序错误),表示dest指向的值随时有可能被别的程序修改,所以每次使用的时候需要重新读取该变量,根据linux下嵌入汇编的规则,对上述代码解释如下:
指令:
cmpxchgq %1,(%3)为要执行的指令,指令之间使用双引号,本方法中使用的比较并交换指令,并需要操作数1和操作数3(%1和%3)
输出:
第一个冒号后定义输出,=表示输出,a表示累加器a,"=a" (exchange_value)表示交换指令执行后,累加器a的值会赋给变量exchange_value
输入:
第二个冒号后面定义输入,"r" (exchange_value)表示将exchange_value的值存在寄存器中,并由指令中的%1引用,"a" (compare_value)表示将compare_value放在累加器a中,"r" (dest)表示dest指针值放到寄存器中,由指令中的%3引用,"r" (mp)表示mp放入寄存器中,由%4引用。

寄存器列表修饰(第三个冒号后面):
cc表示可以修改条件码寄存器,memory表示指令以不可预测的方式修改了内存。
指令成功执行后结果:
exchange_value的值为累加器的值(即compare_value的值),dest指针不变,只是指针指向的内存单元的数据变成了exchange_value,所以,exchange_value和dest指向的内存的内容做了交换,cmpxchg函数最后返回compare_value的值

四 、总结

从 上面的WINDOWS和LINUX平台对CAS的实现来看,本质上都是利用CPU的比较并交换指令,只是不同的平台由于内嵌汇编语法不同导致了不同的实现,但是在JAVA调用看来,不论何种平台,最终达到的效果是一样的,调用方式也相同,java也不需要关心底层使用何种平台的实现。这是JAVA跨平台的好处。

五 、相关链接

 嵌入汇编说明:
http://www.ibm.com/developerworks/cn/linux/sdk/assemble/inline/index.html
linux下汇编说明:
http://www.ibm.com/developerworks/cn/linux/l-assembly/ 
openjdk下载地址:
http://download.java.net/openjdk/jdk8/
                                             
0 0