天天看点

原子操作 CAS CompareAndSwap参考1. AtomicInteger实现CAS2. 底层原子操作:Unsafe类3. volatile + AtomicIntegerFieldUpdater实现CAS:netty中的应用4. ABA问题解决ABA问题: AtomicStampedReference<T>

参考

  • Java CAS ABA问题发生的场景分析 提到了ABA问题
  • Unsafe$compareAndSwapInt 提到AtomicInteger.compareAndSwapInt调用了Unsafe.compareAndSwapInt。后者是用valueOffset(变量偏移量)来定位变量的。
  • cas和aba问题 讲了AtomicStampedReference的实现
  • valueOffset的作用和unsafe.compareAndSwapObject的比较原理 讲了valueoffset只是相对于"this"对象的相对偏移量,unsafe.compareAndSwapObject只是比较内存地址是否相等。

1. AtomicInteger实现CAS

使用

AtomicInteger

AtomicBoolean

等原子操作类可以完成原子操作。它的各种操作都是基于

Unsafe

类的,你可以看到函数的画风都是下面这样:

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

复制

Unsafe

的实现是native方法,无法查看源码:

// Unsafe.java
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);           

复制

2. 底层原子操作:Unsafe类

参考Unsafe类

Unsafe涉及底层硬件级别的原子操作,其函数大多是native的,如下:

// Unsafe.java
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);           

复制

程序员不能直接调用它,只有授信的代码才能获得该类的实例,当然JDK库里面的类是可以随意使用的。

我们只需要知道,它是原子操作的底层实现即可。

3. volatile + AtomicIntegerFieldUpdater实现CAS:netty中的应用

参考张龙netty教程的P.81~P.84

先说结论:volatile + AtomicIntegerFieldUpdater的实现方案相比AtomicInteger能节省更多内存空间。

3.1 两种int的CAS实现

  1. AtomicInteger

    类实现了CAS,但如果频繁使用其实例,会占用不少内存:64位机器上,一个

    AtomicInteger

    对象地址占用8个字节,其内部维护的int值

    value

    占用4个字节。也就是说,在每个用到CAS操作的整形值的地方,都需要维护一个

    AtomicInteger

    对象,也就是占用4+8=12字节。
  2. 但如果能用

    volatile

    +

    AtomicIntegerFieldUpdater

    的方法实现,就能减少存储。你只需要维护一个静态变量

    AtomicIntegerFieldUpdater

    ,并为需要用到CAS操作的int值声明为

    volatile

    即可。只需要先分配一个静态变量,然后在每个用到CAS操作的整形值的地方,都只需要维护一个

    volatile int

    变量,也就是占用4字节。

3.2 netty的CAS实现方案的选择

在netty中,需要频繁创建/销毁大量的ByteBuf对象,用于存储信息。为了及时销毁ByteBuf对象,需要使用引用计数。netty构建了

AbstractReferenceCountedByteBuf

,实现了引用计数的功能。

在程序运行的过程中,会创建大量的

AbstractReferenceCountedByteBuf

实例,每个需要在其内部维护一个整形值,代表引用计数。实例可能会被不同对象同时引用,当它不被引用时,应当及时释放空间。为了让引用计数在多线程下有序地增减,引用计数应当用CAS实现。

那么我们要使用

AtomicInteger

实现CAS吗?

  • AbstractReferenceCountedByteBuf

    实例里的引用计数用一个

    AtomicInteger

    变量实现,确实简单。但这样每个实例都要为它分配4+8=12字节的空间。
  • 但如果我们用

    volatile int

    +

    AtomicIntegerFieldUpdater

    ,只需先为静态变量

    AtomicIntegerFieldUpdater

    分配空间,然后每个实例只需要占用4字节即可。能节约很多内存。

    正因为如此,netty使用了

    AtomicIntegerFieldUpdater

    来实现引用计数的CAS。

3.3 从netty源码学习CAS的实现

我们看下源码:

// AbstractReferenceCountedByteBuf.java
public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {
    private static final long REFCNT_FIELD_OFFSET;
    private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater =
            AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");

    private volatile int refCnt = 1;
    ...
    @Override
    public ByteBuf retain(int increment) {
        return retain0(checkPositive(increment, "increment"));
    }
    private ByteBuf retain0(final int increment) {
        int oldRef = refCntUpdater.getAndAdd(this, increment);
        ...
        return this;
    }
    ...
}           

复制

AbstractReferenceCountedByteBuf

通过retain和release来增加、释放引用。而这两个函数是基于

refCntUpdater

refCnt

的CAS操作。

这样,每个

AbstractReferenceCountedByteBuf

实例只需要为

refCnt

分配4字节空间即可,相比于

AtomicInteger

的12字节,节省了很多空间。

4. ABA问题

参考什么是CAS问题

AtomicInteger、AtomicReference等类会带来ABA问题。

ABA问题,就是要维护的变量被替换后,又设置回来。类实例将无法辨别它被替换过。

举个例子,假设有一个变量x:

  1. 线程1试图用cas把x从A设置为C,所以它先查询x的值。(在这瞬间,线程切换)
  2. 线程2用cas把x设置为B
  3. 线程2用cas把x设置为A
  4. (线程切换回来)线程1查询到x的值为A,于是cas理所当然地把x改为了C。

问题是:线程1在查询x的过程中,x的值已经经历了A->B->A的转变,而线程1对此无所知。这就是ABA问题了。

4.1 AtomicReference的ABA问题

我们看到compareAndSet的源码:

// AtomicReference.java

/**
 * Atomically sets the value to the given updated value
 * if the current value {@code ==} the expected value.
 * @param expect the expected value
 * @param update the new value
 * @return {@code true} if successful. False return indicates that
 * the actual value was not equal to the expected value.
 */
public final boolean compareAndSet(V expect, V update) {
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}           

复制

根据注释,它的比较是基于地址数值比较的,而与"equals"方法没有关系。stackoverflow上的问题 也印证了这一点。

也就是说,AtomicReference的ABA问题指的是"其引用可能会改变",而不是指"引用的变量的值可能会改变"。

解决ABA问题: AtomicStampedReference<T>

AtomicStampedReference