Java并发编程 --- CAS与原子变量

书接上回,我们谈到了Unsafe类,那就不得不谈谈CAS了。

CAS

概述

CAS全称为Compare-And-Swap,对比交换。它是一条CPU原子指令作用在于让CPU比较两个值是否相等,然后更新某个值。

CAS是靠硬件实现的。CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁。

实现保证-Unsafe

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

CAS操作的参数 → 内存位置、预期原值及新值。

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

核心原理 - cmpxchg指令

UnSafe类中调用的CAS实际执行的JNI本地方法,该JNI方法本质上是调用cmpxchg指令。

指令格式: dest 目标操作数 src 修改操作数
带前缀lock保证原子性
lock cmpxchg dest,src

cmpxchg的原子性保证

1、当执行cmpxchg会通过锁总线和缓存锁定来保障原子性。

锁总线:指令执行期间锁定内存总线,阻止其他CPU核心访问内存。

缓存锁定:如果目标内存地址在缓存中处于独占状态(MESI协议),则直接锁缓存,无需总线锁。

cmpxchg的可见性保证

1、缓存一致协议(MESI协议)

现代 CPU 通过 MESI协议维护多核缓存一致性。

当cmpxchg修改内存值后,其他 CPU 核心会通过该协议感知到缓存行的失效,强制从主存或上级缓存重新加载最新值。

2、隐式内存屏障

cmpxchg 指令本身包含 ​隐式内存屏障​(如 lock 前缀),确保指令执行前后的读写操作顺序不被重排序,且修改后的值立即对其他线程可见。

问题

ABA问题

如果只是检查值的话,如果我们需要把一个值从A→C,那如果在这期间有其他线程将A→B→A,那么CAS检查之后发现还是为A,所以会成功更新为C。

解决思路:使用版本号

竞争激烈的话开销大对CPU不友好

如果存在大量竞争,那么会导致很多线程一直在自旋,那么对CPU来说比较不友好。

解决思路:如果预估资源竞争比较激烈,那么使用悲观锁会比较好。

只能保证一个共享变量的原子操作

当对一个共享变量执行操作时,我们可以保证它的原子操作。但当超过一个时,就无法保证。

原子变量

AtomicStampedReference

为什么先谈它呢,因为它解决了ABA问题。

解决思路:引入版本号

public class AtomicStampedReference<V> {
    private static class Pair<T> {
        final T reference;  //维护对象引用
        final int stamp;  //用于标志版本
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
    private volatile Pair<V> pair;
    
    /**
      * expectedReference :更新之前的原始值
      * newReference : 将要更新的新值
      * expectedStamp : 期待更新的标志版本
      * newStamp : 将要更新的标志版本
      */
    public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
        // 获取当前的(元素值,版本号)对
        Pair<V> current = pair;
        return
            // 引用没变
            expectedReference == current.reference &&
            // 版本号没变
            expectedStamp == current.stamp &&
            // 若新引用等于旧引用且新版本号等于旧版本号,执行到这里就结束了
            ((newReference == current.reference &&
            newStamp == current.stamp) ||
            // 构造新的Pair对象并CAS更新
            casPair(current, Pair.of(newReference, newStamp)));
    }

    private boolean casPair(Pair<V> cmp, Pair<V> val) {
        // 调用Unsafe的compareAndSwapObject()方法CAS更新pair的引用为新引用
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }

AtomicInteger

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
 
static {
  try {
     //用来获取value字段相对于当前对象的"起始地址"偏移量
     valueOffset = unsafe.objectFieldOffset
         (AtomicInteger.class.getDeclaredField("value"));
  } catch (Exception ex) { throw new Error(ex); }
}

//volatile保证了各线程对该值的可见性
private volatile int value;

//返回当前的值
public final int get() {
    return value;
}

//先加后再返回值
public final int addAndGet(int delta) {
    return U.getAndAddInt(this, VALUE, delta) + delta;
}
//进行加法 但是只需要返回加前的值
public final int getAndAdd(int delta) {
    return U.getAndAddInt(this, VALUE, delta);
}

@IntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
       //getIntVolatile 本地方法:通过偏移量获取当前的内存值
       v = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));//循环 乐观锁重试机制
    return v;
}

@IntrinsicCandidate
public final boolean weakCompareAndSetInt(Object o, long offset,
                                              int expected,
                                              int x) {
    return compareAndSetInt(o, offset, expected, x);
}

//本地方法 判断并替换
@IntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
                                             int expected,
                                             int x);
posted @ 2024-11-07 14:10  ayu0v0  阅读(32)  评论(0)    收藏  举报