首页 > 编程语言 >Java魔法类之Unsafe(cas)底层实现

Java魔法类之Unsafe(cas)底层实现

时间:2023-02-28 23:24:59浏览次数:60  
标签:compare Java exchange cas Unsafe value dest oop offset

一、JVM层

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

sun.misc.UnsafeCAS方法如下:

/**
 * CAS
 * @param o         包含要修改field的对象
 * @param offset    对象中某field的偏移量
 * @param expected  期望值
 * @param update    更新值
 * @return          true | false
 */
public final native boolean compareAndSwapObject(Object o, long offset, Object expected,
        Object update);

public final native boolean compareAndSwapInt(Object o, long offset, int expected, int update);

public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);

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

#define FN_PTR(f) CAST_FROM_FN_PTR(void*, &f)

{CC"compareAndSwapObject", CC"("OBJ"J"OBJ""OBJ")Z",  FN_PTR(Unsafe_CompareAndSwapObject)},
{CC"compareAndSwapInt",  CC"("OBJ"J""I""I"")Z",      FN_PTR(Unsafe_CompareAndSwapInt)},
{CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z",      FN_PTR(Unsafe_CompareAndSwapLong)},
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); // 更新对象
  // 根据偏移量offset获取内存中的具体位置
  HeapWord* addr = (HeapWord *)index_oop_from_field_offset_long(p, offset); 
  oop res = oopDesc::atomic_compare_exchange_oop(x, addr, e, true); // 调用方法执行CAS操作
  jboolean success  = (res == e);  // 如果返回值res==e则表明满足compare条件,swap成功
  if (success)
    update_barrier_set((void*)addr, x); // 更新memory barrier
  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

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

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

// 声明在hotspot/src/share/vm/oops/oop.hpp
static oop atomic_compare_exchange_oop(oop exchange_value,
                                       volatile HeapWord *dest,
                                       oop compare_value,
                                       bool prebarrier = false);

// 定义在hotspot/src/share/vm/oops/oop.inline.hpp
inline oop oopDesc::atomic_compare_exchange_oop(oop exchange_value,
                                                volatile HeapWord *dest,
                                                oop compare_value,
                                                bool prebarrier) {
  if (UseCompressedOops) {
    if (prebarrier) {
      update_barrier_set_pre((narrowOop*)dest, exchange_value);
    }
    // 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 {
    if (prebarrier) {
      update_barrier_set_pre((oop*)dest, exchange_value);
    }
    return (oop)Atomic::cmpxchg_ptr(exchange_value, (oop*)dest, compare_value);
  }
}

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)函数虽然有很多重载函数,但最终都是调用的下面的函数:

// 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);  // 当前值cur的地址
  jint new_val = cur;
  jbyte* new_val_as_bytes = (jbyte*)(&new_val);  // new_val地址
  // new_val存exchange_value,后面修改则直接从new_val中取值
  new_val_as_bytes[offset] = exchange_value;
  // 比较当前值与期望值,如果相同则更新,不同则直接返回
  while (cur_as_bytes[offset] == compare_value) {
    // 调用汇编指令cmpxchg执行CAS操作,期望值为cur,更新值为new_val
    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];
}

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

// 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();
  //LOCK_IF_MP(%4) 在多处理器环境下,为cmpxchgl指令添加lock前缀,以达到内存屏障的效果
  //cmpxchgl指令是包含在x86架构及IA-64架构中的一个原子条件指令,
  //它会首先比较 dest 指针指向的内存值是否和compare_value的值相等,
  //如果相等,则双向交换dest与exchange_value,否则就单方面地将dest指向的内存值交给exchange_value。
  //这条指令完成了整个CAS操作,因此它也被称为CAS指令。
  __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;

cmpxchgl的详细执行过程:

  • 首先,输入是"r" (exchange_value), “a” (compare_value), “r” (dest), “r” (mp),表示compare_value存入eax寄存器,而exchange_value、dest、mp的值存入任意的通用寄存器。嵌入式汇编规定把输出和输入寄存器按统一顺序编号,顺序是从输出寄存器序列从左到右从上到下以“%0”开始,分别记为%0、%1···%9。也就是说,输出的eax是%0,输入的exchange_value、compare_value、dest、mp分别是%1、%2、%3、%4。
  • 因此,cmpxchg %1,(%3)实际上表示cmpxchg exchange_value,(dest)
    需要注意的是cmpxchg有个隐含操作数eax,其实际过程是先比较eax的值(也就是compare_value)和dest地址所存的值是否相等。
  • 输出是"=a" (exchange_value),表示把eax中存的值写入exchange_value变量中。
    Atomic::cmpxchg这个函数最终返回值是exchange_value,也就是说,如果cmpxchgl执行时compare_value和dest指针指向内存值相等则会使得dest指针指向内存值变成exchange_value,最终eax存的compare_value赋值给了exchange_value变量,即函数最终返回的值是原先的compare_value。
  • 此时Unsafe_CompareAndSwapInt的返回值(jint)(Atomic::cmpxchg(x, addr, e)) == e就是true,表明CAS成功。如果cmpxchgl执行时compare_value和(dest)不等则会把当前dest指针指向内存的值写入eax,最终输出时赋值给exchange_value变量作为返回值,导致(jint)(Atomic::cmpxchg(x, addr, e)) == e得到false,表明CAS失败。

源码的核心点

  • 不管是Hotspot中的Atomic::cmpxchg方法,还是Java中的compareAndSwapInt方法,它们本质上都是对相应平台的CAS指令的一层简单封装。
  • CAS指令作为一种硬件原语,有着天然的原子性,这也正是CAS的价值所在。

参考文章

标签:compare,Java,exchange,cas,Unsafe,value,dest,oop,offset
From: https://www.cnblogs.com/ciel717/p/17159398.html

相关文章

  • Java连接数据库技术-JDBC
    课程简介和目标 Java数据库连接,(JavaDatabaseConnectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据......
  • Java连接数据库技术-JDBC
    课程简介和目标 Java数据库连接,(JavaDatabaseConnectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据......
  • 算法刷题-简单密码-JAVA
    0x00引言为获取一个良好的算法思维,以及不再成为一个脚本小子,争取每天一道算法题,培养自己的逻辑思维,温顾各类型语言语法知识。题解只写自己理解的解法,其他解法不再增加。......
  • java学习日记20230227-dos原理
    DOS原理 磁盘操作系统disoperatingsystemmdc:\\temp创建文件夹rdc:\\jyltemp移除文件夹相对路径和绝对路径 相对路径:从当前目录开始定位形成的路径绝对路径......
  • 算法刷题-统计大写字母个数-JAVA
    0x00引言为获取一个良好的算法思维,以及不再成为一个脚本小子,争取每天一道算法题,培养自己的逻辑思维,温顾各类型语言语法知识。题解只写自己理解的解法,其他解法不再增加。......
  • (未完成)JAVAWEB学习——
    一、Servlet开发1.sun公司提供的一种动态web资源开发技术,本质上就要是一段Java小程序,可以将Servlet加入到容器中运行Servlet。*servlet容器--能够运行servlet的环境就......
  • 算法刷题-等差数列-JAVA
    0x00引言为获取一个良好的算法思维,以及不再成为一个脚本小子,争取每天一道算法题,培养自己的逻辑思维,温顾各类型语言语法知识。题解只写自己理解的解法,其他解法不再增加。......
  • 算法刷题-求最大连续bit数-JAVA
    0x00引言为获取一个良好的算法思维,以及不再成为一个脚本小子,争取每天一道算法题,培养自己的逻辑思维,温顾各类型语言语法知识。题解只写自己理解的解法,其他解法不再增加。......
  • 算法刷题-求int型正整数在内存中存储时1的个数-JAVA
    0x00引言为获取一个良好的算法思维,以及不再成为一个脚本小子,争取每天一道算法题,培养自己的逻辑思维,温顾各类型语言语法知识。题解只写自己理解的解法,其他解法不再增加。......
  • 算法刷题-查找组成一个偶数最接近的两个素数-JAVA
    0x00引言为获取一个良好的算法思维,以及不再成为一个脚本小子,争取每天一道算法题,培养自己的逻辑思维,温顾各类型语言语法知识。题解只写自己理解的解法,其他解法不再增加。......