Java CAS详解

2年前 (2022) 程序员胖胖胖虎阿
324 0 0

CAS,即Compare and Swap,是基于硬件级别的指令实现的同步原语,Java并发包java.utile.concurrent许多同步类基于CAS构建,因此可见CAS的重要性;

定义

处理器(包括 Intel 和 Sparc 处理器)使用的最通用的方法是实现名为比较并转换或CAS的原语,在 Intel 处理器中,比较并交换通过指令的 cmpxchg系列实现。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值V与预期原值A相匹配,那么处理器会自动将该位置值V更新为新值B,否则,处理器不做任何操作,整个操作保证了原子性,即在对比V==A后、设置V=B之前不会有其他线程修改V的值。

Unsafe类

Java无法直接访问底层操作系统,而是通过本地native方法来访问,但还是留了一个后门-Unsafe类,提供了一些低层次操作,如直接内存访问等,Unsafe类也提供了CAS操作的native方法:

/** 拿对象o在内存偏移offset处的对象与expected比较,如果相等,则设置o.offset=x并返回true,否则返回false */
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
/** 拿对象o在内存偏移offset处的long值与expected比较,如果相等则设置o.offset=x */
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
/** 拿对象o在内存偏移offset处的int值与expected比较,如果相等则设置o.offset=x */
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);

/** 获取字段f的偏移量 */
public native long objectFieldOffset(Field f);
/** 获取静态field在对象中的偏移量 */
public native long staticFieldOffset(Field f);
                                                     

比如AbstractQueuedSynchronizer类中用到CAS实现state值的更新:

long stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));

// CAS方式修改state字段的值
protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

此外,Unsafe类还基于这几个native方法封装了几个给atomic原子类调用的方法:拿getAndAddInt分析,首先取内存中的值,然后执行CAS操作,如果成功则返回修改前的值;如果取完内存值后执行CAS失败,比如被其他线程修改了值,则重新取内存中的值,继续CAS,直到成功,如果竞争比较激烈,可能会循环次数过多;这里可以引申出乐观锁悲观锁的含义,比如某个线程要对变量进行add操作,悲观锁认为期间一定会有其他线程修改变量,于是会在整个修改期间加锁,比如synchronized;而乐观锁认为不会有其他线程修改变量,getAndAddInt正是乐观锁,用CAS代替锁,如果出现竞争,则用自旋的方式等待;

public final int getAndAddInt(Object o, long offset, int delta) {
	int v;
    do {
    	// 获取内存中的值
         v = getIntVolatile(o, offset);
     } while (!compareAndSwapInt(o, offset, v, v + delta));
     return v;
 }
 public final long getAndAddLong(Object o, long offset, long delta)
 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)

CAS在原子类中的应用—AtomicInteger为例

AtomicInteger可以理解为在并发场景下基于CAS乐观锁实现了int共享变量更新的线程安全,常见方法如下:

public final int getAndSet(int newValue) {
    return unsafe.getAndSetInt(this, valueOffset, newValue);
}

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

public final int getAndDecrement() {
    return unsafe.getAndAddInt(this, valueOffset, -1);
}

CAS存在的问题

  1. 效率问题:前面提到,如果存在多个线程竞争,可能导致CAS失败,此时可能需要循环(自旋)执行CAS,竞争激烈情况下会对性能有一定影响;

  2. ABA问题:CAS过程中其他线程把变量从A改成B,然后又改回A,CAS判断值没变于是执行更新操作,但事实上值是被修改了的,与设计原语不符,atomic包引入AtomicStampReference类解决ABA问题,每次变量更新的时候,将变量的版本号+1,之前的ABA问题中,变量经过两次操作以后,变量的版本号就会由1变成3,也就是说只要线程对变量进行过操作,变量的版本号就会发生更改,从而解决了ABA问题;但实际应用中ABA问题如果对业务逻辑不会造成影响,可以忽略;

参考文章

https://baike.baidu.com/item/CAS/7371138?fr=aladdin
https://www.cnblogs.com/wq-9/articles/15682679.html
https://www.jianshu.com/p/238fa270c7aa

版权声明:程序员胖胖胖虎阿 发表于 2022年10月11日 下午1:08。
转载请注明:Java CAS详解 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...