Compare and Swap

来自WHY42
imported>Riguz2020年5月20日 (三) 00:00的版本 (CAS:Compare and Swap,即比较再交换。jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)

CAS:Compare and Swap,即比较再交换。jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。

实现机制

在Java中很多原子操作是通过CAS实现的,是一种无锁的方式,比如AtomicIntger的自增操作:

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

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

这里的操作调用了Unsafe类来实现,而这个Unsafe类里面的实代码是这样的:

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

而最终是用到了native的方法compareAndSwapInt,这部分的源码可以在OpenJDK源码中找到,而最终是会调用到CMPXCHG这样到CPU指令来完成CAS。

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);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

其中,源码位于:

ABA问题

使用CAS一个问题就是可能出现A-B-A问题,因为CAS到逻辑就是,假设更新到时候判断出值跟期望的一致那么就会进行更改;但实际值跟期望的一致并不能代表值没有发生过变化。可能的场景就是,值被改成别的然后又改回来了,就是从A-B,然后又变成A。

解决这个问题的思路在于,想办法标记出变化。通常的做法是利用版本号(而不是值),保证版本号每次都会变化。JDK中提供了AtomicStampedReference来解决A-B-A问题,实现如下:

 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) ||
          casPair(current, Pair.of(newReference, newStamp)));
 }

References: