美文网首页
原子操作 CAS CompareAndSwap

原子操作 CAS CompareAndSwap

作者: 不存在的里皮 | 来源:发表于2019-04-28 05:14 被阅读0次

参考

1. AtomicInteger实现CAS

使用AtomicIntegerAtomicBoolean等原子操作类可以完成原子操作。它的各种操作都是基于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来增加、释放引用。而这两个函数是基于refCntUpdaterrefCnt的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

相关文章

  • 原子操作 CAS CompareAndSwap

    参考 Java CAS ABA问题发生的场景分析 提到了ABA问题 Unsafe$compareAndSwapIn...

  • (十七)CAS

    比较并交换:通常指CompareAndSwap或CompareAndSet,乐观锁 1、CAS是一个原子操作,比较...

  • CAS/ABA

    CAS:CompareAndSwap,是一种原子操作,用于在多线程环境中保证不被打断的数据交换操作,用旧值和主内存...

  • CAS操作

    CAS操作 CAS是compareAndSwap的缩写,它是一条CPU并发原语。由于CAS是一种系统原语,属于系统...

  • java如何实现原子操作CAS

    在Java中可以通过锁和循环CAS的方式来实现原子操作。 使用循环CAS实现原子操作 JVM中的CAS操作正是利用...

  • CAS+ABA+Unsafe+悲观锁和乐观锁

    1 CAS CAS,即compare and swap。CAS操作是原子操作,在多线程中执行CAS操作可以实现同步...

  • 多线程笔记4-CAS基本原理

    什么是CAS? CAS为 compare and swap (判断并且交换) 什么是原子操作? 原子操作的基本概念...

  • java多线程(三)原子操作CAS

    CAS(Compare And Swap)什么是原子操作?如何实现原子操作CAS的原理:利用了现代处理器都支持的C...

  • Java基础-线程 (三)-锁

    CAS是什么? 了解CAS之前,我们先了解变量的2大特性。 原子操作(原子性):对于操作A,要么执行完,要么完全不...

  • Java-多线程-Atomic&Unsafe魔术类

    一、原子操作 Java中可以通过锁和循环CAS的方式来实现原子操作。JVM中的CAS操作正是利用了上文中提到的处理...

网友评论

      本文标题:原子操作 CAS CompareAndSwap

      本文链接:https://www.haomeiwen.com/subject/uihrnqtx.html