从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调用时,不同平台最终达到的效果是一致的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

弯_弯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值