JDK 无锁原子操作 Unsafe CAS自旋 实现线程安全

博文目录


Java魔法类:Unsafe应用解析

CAS

什么是CAS? 即比较并替换,实现并发算法时常用到的一种技术。

CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。

CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法底层实现即为CPU指令cmpxchg。

Unsafe CAS

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

VMSupportsCS8

一场 Atomic XXX 的魔幻之旅

compareAndSwapInt 和 compareAndSwapLong 在 java 层面是非常相似的, 但是 C++ 底层实现相差很多

在 /jdk8u-dev/hotspot/src/share/vm/prims/unsafe.cpp 中定义了 CAS 中的方法 UNSAFE_ENTRY在这里插入图片描述
相比于 compareAndSwapInt 方法,compareAndSwapLong 方法包含了 条件编译 SUPPORTS_NATIVE_CX8

即方法首先会判断是否支持 SUPPORTS_NATIVE_CX8 ,就是判断机器是否支持 8 字节的 cmpxchg 这个 CPU 指令。如果硬件不支持,就会判断 JVM 是否支持,如果 JVM 也不支持,就表明这个操作不是 Lock Free 的。此时JVM 会使用显示锁例如 synchronized 来接管

在 AtomicLong.java 中也能看到如下定义

// 记录底层 JVM 是否支持 long 的无锁 compareAndSwap。虽然 Unsafe.compareAndSwapLong 方法在任何一种情况下都有效,但应该在 Java 级别处理一些构造以避免锁定用户可见的锁。
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
.. 返回底层 JVM 是否支持 long 的无锁 CompareAndSet。仅调用一次并缓存在 VM_SUPPORTS_LONG_CAS 中
private static native boolean VMSupportsCS8();

32 位 CPU 肯定不支持 8 字节 64 位数字的 cpmxchg 指令

AtomicLong 的 lazySet 使用了 Unsafe.putOrderedLong 方法, 同样有这个判断
在这里插入图片描述

Unsafe 无锁 CAS+自旋 原子操作

Unsafe 中还提供了基于 CAS 的自旋无锁原子操作如下

// 获取旧值, 并设置新值
public final int getAndSetInt(Object o, long offset, int newValue) {
    
    }
public final long getAndSetLong(Object o, long offset, long newValue) {
    
    }
public final Object getAndSetObject(Object o, long offset, Object newValue) {
    
    }
// 获取旧值, 并给旧值增加delta
public final int getAndAddInt(Object o, long offset, int delta) {
    
    }
public final long getAndAddLong(Object o, long offset, long delta) {
    
    }

// 通过CAS+自旋的方式, 保证获取旧值和设置新值两个操作合并, 使其具有原子操作的效果
public final int getAndSetInt(Object o, long offset, int newValue) {
    
    
    int value;
    do {
    
    
    	// 获取到最新的value
        value = this.getIntVolatile(o, offset);
        // 原子指令, 比较期望值与更新值
        // 当两者相同时, 说明获取value后到cas操作间value被修改过了, 修改旧值为新值, 返回true, while条件为false, 结束循环
        // 当两者不同时, 说明获取value后到cas操作间value没被修改过, 直接返回false, while条件为true, 重新获取最新的value并cas, 直到成功
    } while(!this.compareAndSwapInt(o, offset, value, newValue));
    // 返回旧值
    return value;
}

Unsafe 无锁 CAS+自旋 原子操作 的弊端

  • ABA问题: a线程获取到value为1, b线程将value设置为2, c线程将value又设置为1, a线程cas操作, 期望值和原值value相同, 认为value没有被修改过, 操作成功, 但其实value被修改过了. 在某些情况下可能存在问题
  • 自旋时间过长: 当并发高的时候, 原值可能会被频繁修改, 导致很多线程需要大量自旋, 消耗大量cpu资源, 损耗性能. 所以CAS+自旋不太适用于超高并发场景

典型应用

CAS 在 java.util.concurrent.atomic 相关类、Java AQS、ConcurrentHashMap 等实现上有非常广泛的应用

JDK java.util.concurrent.atomic

猜你喜欢

转载自blog.csdn.net/mrathena/article/details/125217795