当前位置: 首页 > article >正文

从JVM 源码的角度深度剖析CAS是如何实现原子性的

CAS 介绍

CAS(Compare And Swap,比较并交换),通常指的是这样一种原子操作:针对一个变量,首先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值。

Unsafe类中的CAS方法

sun.misc.Unsafe 类是一个不安全的类,提供了一组用于执行底层的,不安全操作的方法。

完成java应用层的CAS操作主要涉及Unsafe方法的调用,具体如下:
(1) 获取Unsafe实例
Unsafe类是一个final修饰的不允许继承的最终类,而且其构造函数是private类型的方法,如下所示:

public final class Unsafe {
	  private Unsafe() {}
}

因此我们无法在外部对Unsafe进行实例化,可以通过反射的方式自定义地 获取unsafe实例的辅助方法,代码如下:

 public static Unsafe getUnsafe() {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            return (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

(2)调用Unsafe提供的CAS方法,这些方法主要封装了底层CPU的CAS原子操作。

Unsafe 提供的CAS方法包含4个操作数-字段所在的对象、字段内存位置、预期原值,新值;在执行Unsafe的CAS方法时,这些方法首先将内存位置的值与预期值比较,如果相匹配,那么CPU 会自动将该内存位置的值更新为新值,并返回true,如果不匹配,CPU不做任何操作,并返回false。部分源码如下:

/*
* 比价并交换的原子方法
 * @param o 需要操作的字段所在的对象
 * @param offset 需要操作的字段的偏移量(相对的,相对于对象头)
 * @param expected 期望值(旧的值)
 * @param x  更新值(新的值)
 * @return {@code true} 如果成功返回true
 */
public final native boolean compareAndSetInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSetObject(Object o, long offset, Object expected,Object x);
public final boolean compareAndSetShort(Object o, long offset, short expected,short x) ;

Unsafe的CAS操作会将第一个参数(对象的指针、地址)与第二个参数(字段偏移量)组合在一起,计算出最终的内存操作地址。

(3)调用Unsafe提供的字段偏移量方法,这些方法用于获取对象中的字段偏移量,此偏移量值需要作为参数提供给CAS操作。

Unsafe 提供的获取字段(属性)偏移量的相关操作主要如下:

/*
* 获取静态属性Field在Class对象中的偏移量,在CAS中操作静态属性时会用到这个偏移量。
*@param f 需要操作字段的反射
*@return  字段的偏移量
/
public long staticFieldOffset(Field f);
/*
* 获取非静态Field(非静态属性)在Object实例中的偏移量,在CAS中操作对象的非静态属性时会用到这个偏移量。
*@param f 需要操作字段的反射
*@return  字段的偏移量
/
public long objectFieldOffset(Field f);

使用CAS 进行无锁编程

CAS 是一种无锁算法,该算法关键依赖两个值-期望值和新值,使用CAS进行无所编程的步骤如下:
(1)获得字段的期望值(oldValue)
(2)计算出需要替换的新值(newValue)
(3)通过CAS将新值(newValue)放在字段的内存地址上,如果CAS失败就重新(1),(2)步骤,一直到CAS成功,这种重复俗称CAS自旋.

伪代码:

do{
  获得字段的期望值(oldValue)
  计算出需要替换的新值(newValue)
} while(!(CAS(内存地址,oldValue,newValue))

当CAS 将内存地址的值与预期值进行比较时,如果相等,就证明内存地址的值没有被修改,可以替换成新值,然后继续往下运行,如果不相等,就说明内存地址的值已经被修改,放弃替换操作,然后重新自旋.

当并发修改的线程少,冲突出现的机会少时,自旋的次数也会很少,CAS的性能会很高,当并发修改的线程多,冲突出现的机会多时,自旋的次数也会很多,CAS的性能会大大降低. 所以,提升CAS无锁编程效率的关键在于减少冲突的机会

JNI的具体实现

Unsafe中的原子性方法有三个,如下:

  /**
     * 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);

    /**
     * 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 compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);

    /**
     * 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 compareAndSwapLong(Object o, long offset,
                                                   long expected,
                                                   long x);

在剖析原子性方法的实现前,我们先来看看原子类委托方法,代码位于oop.hpp,文件路径为Hotspot\src\share\vm\oops。使用oopDesc::

public:
	static oop atomic_compare_exchange_oop(oop exchange_value,
                                         volatile HeapWord *dest,
                                         oop compare_value,
                                         bool prebarrier = false);

在oop.inline.cpp中提供了实现,文件路径为hostspot/src/share/vm/oops

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::cmpxchg或Atomic::cmpxchg_ptr,所以最终的实现由Atomic类确定。

由于java的原子类是由Unsafe.java类实现的,我们就以CompareAndSwapInt方法为例是如何实现原子类的。文件路径为Hotspot\src\share\vm\prims\unsafe.cpp

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  //将java对象转化为JVM的oop(ordinary object pointer,普通对象指针)
  oop p = JNIHandles::resolve(obj);
  //将oop对象 转换为堆地址,通过oop对象和地址偏移量确定
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  //重点看Atomic::cmpxchg
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

核心方法是Atomic::cmpxchg,这个方法所属的类文件是在OS_CPU目录下,不同平台的实现不同,与CPU操作有关,先来看下linux平台下Atomic::cmpxchg方法的实现,文件路径为hotspot\src\os_cpu\liunx_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();
  //cmpxchgq %1,(%3)为要执行的指令,指令之间使用双引号,本方法中使用的比较并交换指令,并需要操作数1和操作数3(%1和%3) 输出:
  //  第一个冒号后定义输出,
        //=表示输出,a表示累加器a,"=a" (exchange_value)表示交换指令执行后,累加器a的值会赋给变量
  //第二个冒号后面定义输入,
       //"r" (exchange_value)表示将exchange_value的值存在寄存器中,并由指令中的%1引用,
      //"a" (compare_value)表示将compare_value放在累加器a中,
      //"r" (dest)表示dest指针值放到寄存器中,由指令中的%3引用,
      //"r" (mp)表示mp放入寄存器中,由%4引用。
  // 第三个冒号是寄存器列表修饰
    //cc表示可以修改条件码寄存器,
    //memory表示指令以不可预测的方式修改了内存。
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
 
 //输出结果
    累加器的值(即compare_value的值),dest指针不变,只是指针指向的内存单元的数据变成了exchange_value,
   // 所以,exchange_value和dest指向的内存的内容做了交换,cmpxchg函数最后返回compare_value的值    
  return exchange_value; 
}


#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

asm volatile表示linux下内嵌汇编(也兼容ANSIC),并且使用关键字禁止编译器指令重排序优化,而dest使用volatile修饰(禁止编译器优化存取,比如,为了存取速度,将dest的内容存储在寄存器中,当外部程序修改dest指向的内存单元时,寄存器感知不到,还是原来的值,从而导致程序错误),表示dest指向的值随时有可能被别的程序修改,所以每次使用的时候需要重新读取该变量,根据linux下嵌入汇编的规则,

再看看window平台的 Atomic::cmpxchg 方法实现,文件路径 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  //表示把目标地址放入edx寄存器中
    mov ecx, exchange_value //把exchange_value值放入寄存器ecx中。
    mov eax, compare_value // 需要和目标地址指向的值进行比较的值放入寄存器eax中。
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx //执行比较操作并交换,这是一个原子操作
  }
}
#define LOCK_IF_MP(mp) __asm cmp mp, 0  \
                       __asm je L0      \
                       __asm _emit 0xF0 \
                       __asm L0:

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

从liunx和window平台的实现中可知,都调用了LOCK_IF_MP,LOCK_IF_MP是根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。MP是指multiple processor,即多核处理器。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。

总结,CAS 原子性实现是利用CPU的比较并交换指令(cmpxchg),不同的平台由内嵌汇编语法不同导致不同的实现,但是在Java调用时,不同平台最终达到的效果是一致的。


http://www.kler.cn/a/9530.html

相关文章:

  • 界面控件Kendo UI for Angular中文教程:如何构建带图表的仪表板?(一)
  • 21. Drag-Drop拖放操作(二) - 文件、表格和树的拖放实现
  • vivo 游戏中心包体积优化方案与实践
  • 什么是数字图像?
  • 设计模式-七个基本原则之一-单一职责原则 + SpringBoot案例
  • git命令及原理
  • 校区机房物联网数据采集及远程监控5G应用系统方案
  • Spring(Ioc和Bean的作用域)
  • Docker-Compose镜像仓库
  • 【云原生进阶之容器】第六章容器网络6.4.1--Flannel组网方案综述
  • ServletContext
  • 《剑指offer》——从尾到头打印链表
  • 【Python】1分钟就能制作精美的框架图?太棒啦
  • Minio上传html文件
  • 分享10个前端开发者需要掌握的DOM技巧
  • 超越辅助:分享一个基于GPT引擎的免费AI工具
  • 一文解读基于PaddleSeg的钢筋长度超限监控方案
  • 管廊隧道怎么定位人员?分享管廊隧道人员定位系统解决方案
  • ubuntu16.04搭建gitlab
  • 原油期货是什么?原油期货交易盈利技巧有哪些?
  • MyBatis动态SQL教程:灵活处理复杂SQL场景,提升性能与可维护性
  • 二叉树练习题(递归展开图详解哦)
  • 21. 合并两个有序链表(Java)
  • 坦克大战第一阶段代码
  • 电子学会2023年3月青少年软件编程python等级考试试卷(一级)真题,含答案解析
  • 6、springboot快速使用