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);}
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实现
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
- JVM的CAS实现原理解析
- cas工作原理解析
- CAS实现单点登录原理与实例解析
- CAS实现原理
- JVM的底层实现原理
- JVM的底层实现原理
- java CAS原理深度解析
- JAVA CAS原理深度解析
- TS各个表 与 SECTION 的解析 CAS原理
- TS各个表 与 SECTION 的解析 CAS原理
- TS各个表 与 SECTION 的解析 CAS原理
- TS各个表 与 SECTION 的解析 CAS原理
- AtomicInteger的CAS原理
- AtomicInteger的CAS原理
- JVM运行原理解析
- JASIG-CAS原理抓包解析
- JASIG-CAS原理抓包解析2
- CAS框架单点登录原理解析
- 用内联函数代替多个驱动器
- POJ 1850 Code (组合数学)
- 记:四周实习项目--XX医院门诊系统
- 代码之道阅读手记
- 计算MD5
- JVM的CAS实现原理解析
- JAVA之打印九九乘法表
- mac 下管理员无权限修改文件问题以及rootLess内核保护开关
- 寄存器(CPU工作原理)
- JAVA之数组从“A”到“Z”顺序插入元素
- php中的interface和implements及其他
- 获取设备型号
- Android M 新的运行时权限开发者需要知道的一切
- 164. Maximum Gap