深入理解CAS&Atomic原子操作类详解

深入理解CAS&Atomic原子操作类详解

1.CAS介绍

什么是 CAS

CAS(Compare And Swap,比较与交换),是非阻塞同步的实现原理,它是CPU硬件层面的一种指令,从CPU层面能保证"比较与交换"两个操作的原子性。CAS指令操作包括三个参数:内存值(内存地址值)V、预期值E、新值N,当CAS指令执行时,当且仅当预期值E和内存值V相同时,才更新内存值为N,否则就不执行更新,无论更新与否都会返回旧的内存值V,上述的处理过程是一个原子操作。

用Java代码等效实现一下CAS的执行过程:

public class CASDemo {

// 内存中当前的值

private volatile int ramAddress;

/**

* @param expectedValue 期望值

* @return newValue 更新的值

**/

public synchronized int compareAndSwap(int expectedValue, int newValue) {

//TODO 模拟直接从内存地址读取到内存中的值

int oldRamAddress = accessMemory(ramAddress);

//内存中的值和期望的值进行比较

if (oldRamAddress == expectedValue) {

ramAddress = newValue;

}

return oldRamAddress;

}

private int accessMemory(int ramAddress) {

//TODO 模拟直接从内存地址读取到内存中的值

return ramAddress;

}

}

以上伪代码描述了一个由比较和赋值两阶段组成的复合操作,CAS 可以看作是它们合并后的整体——一个不可分割的原子操作,并且其原子性是直接在硬件层面得到保障的。

CAS是一种无锁算法,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。CAS可以看做是乐观锁(对比数据库的悲观、乐观锁)的一种实现方式,Java原子类中的递增操作就通过CAS自旋实现的。

CAS使用

在 Java 中,CAS 操作是由 Unsafe 类提供支持的,该类定义了三种针对不同类型变量的 CAS 操作,如图

它们都是 native 方法,由 Java 虚拟机提供具体实现,这意味着不同的 Java 虚拟机对它们的实现可能会略有不同。

Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。

以 compareAndSwapInt 为例,Unsafe 的 compareAndSwapInt 方法接收 4 个参数,分别是:对象实例、内存偏移量、字段期望值、字段新值。该方法会针对指定对象实例中的相应偏移量的字段执行 CAS 操作。

public class CASTest {

public static void main(String[] args) {

Entity entity = new Entity();

Unsafe unsafe = UnsafeFactory.getUnsafe();

long offset = UnsafeFactory.getFieldOffset(unsafe, Entity.class, "x");

boolean successful;

// 4个参数分别是:对象实例、字段的内存偏移量、字段期望值、字段新值

successful = unsafe.compareAndSwapInt(entity, offset, 0, 3);

System.out.println(successful + "\t" + entity.x);

successful = unsafe.compareAndSwapInt(entity, offset, 3, 5);

System.out.println(successful + "\t" + entity.x);

successful = unsafe.compareAndSwapInt(entity, offset, 3, 8);

System.out.println(successful + "\t" + entity.x);

}

}

public class UnsafeFactory {

/**

* 获取 Unsafe 对象

* @return

*/

public static Unsafe getUnsafe() {

try {

Field field = Unsafe.class.getDeclaredField("theUnsafe");

field.setAccessible(true);

return (Unsafe) field.get(null);

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

/**

* 获取字段的内存偏移量

* @param unsafe

* @param clazz

* @param fieldName

* @return

*/

public static long getFieldOffset(Unsafe unsafe, Class clazz, String fieldName) {

try {

return unsafe.objectFieldOffset(clazz.getDeclaredField(fieldName));

} catch (NoSuchFieldException e) {

throw new Error(e);

}

}

}

测试

针对 entity.x 的 3 次 CAS 操作,分别试图将它从 0 改成 3、从 3 改成 5、从 3 改成 8。执行结果如下:

CAS应用场景

CAS在java.util.concurrent.atomic相关类、Java AQS、CurrentHashMap等实现上有非常广泛的应用。如下图所示,AtomicInteger的实现中,静态字段valueOffset即为字段value的内存偏移地址,valueOffset的值在AtomicInteger初始化时,在静态代码块中通过Unsafe的objectFieldOffset方法获取。在AtomicInteger中提供的线程安全方法中,通过字段valueOffset的值可以定位到AtomicInteger对象中value的内存地址,从而可以根据CAS实现对value字段的原子操作。

下图为某个AtomicInteger对象自增操作前后的内存示意图,对象的基地址baseAddress=“0x110000”,通过baseAddress+valueOffset得到value的内存地址valueAddress=“0x11000c”;然后通过CAS进行原子性的更新操作,成功则返回,否则继续重试,直到更新成功为止。

CAS源码分析

Hotspot 虚拟机对compareAndSwapInt 方法的实现如下:

#unsafe.cpp

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

// 根据偏移量,计算value的地址

jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);

// Atomic::cmpxchg(x, addr, e) cas逻辑 x:要交换的值 e:要比较的值

//cas成功,返回期望值e,等于e,此方法返回true

//cas失败,返回内存中的value值,不等于e,此方法返回false

return (jint)(Atomic::cmpxchg(x, addr, e)) == e;

UNSAFE_END2

核心逻辑在Atomic::cmpxchg方法中,这个根据不同操作系统和不同CPU会有不同的实现。这里我们以linux_64x的为例,查看Atomic::cmpxchg的实现

#atomic_linux_x86.inline.hpp

inline jint

相关探索