Java

通过AtomicInteger来理解CAS

2019-05-12  本文已影响0人  若琳丶

介绍

源码分析

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

    // setup to use Unsafe.compareAndSwapInt for updates
    //JAVA实现CAS算法的类,整个类有关线程安全的操作,都是借助它来实现。
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //变量value的内存首地址的偏移量。
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //存放int值
    private volatile int value;

valueOffset 详解

在不了解一个事物的时候,最直接的是先看它的作用,然后参考注释或者其他资料,进一步用自己的理解抽象出一个概念。

作用:是用来记录 value 本身在内存的偏移地址的,这个记录,也主要是为了在更新操作在内存中找到 value 的位置,方便比较。

也就是说,valueOffset 的核心作用是用来定位 value 在 AtomicInteger 对象中的位置的。

进一步了解:

valueOffset 是一个静态常量,并且在类加载时就被赋值了。那么这个类在编译后的字节码是一定的,但是在堆中存放 AtomicInteger 对象的首地址是随机的,所以这里的偏移应该是相对于对象实例的首地址。

再通过下图进行辅助理解:


AtomicInteger 内存情况

结论:

public class AtomicTest {

    public static void main(String[] args) {
        AtomicInteger a = new AtomicInteger(1);
        AtomicInteger b = new AtomicInteger(100);
        int c = a.addAndGet(5);
        b.addAndGet(4);
        System.out.println(a.get());
        System.out.println(c);
    }
}

通过 debug 可以发现:AtomicInteger 中 valueOffset 的值为 12


valueOffset 值

getAndInt

getAndAdd 方法:
/**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the previous value
     */
    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

我们已经知道了 valueOffset 的意义,那么 getAndAdd() 方法也比较好理解了

valueOffSet 在这里我们可以理解为 value,因为通过 this 和 valueOffSet,我们就可以得到内存中 value 的值。

unsafe 中的 getAndAddInt 方法:

这里将源码中的参数语意化一点,因为源码中直接看变量名不太好理解。这里再把 native 方法 compareAndSwapInt 一并放进来,并进行语义化注释。

    /**
    * 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。
    * 
    * @param obj 需要更新的对象
    * @param offset obj中整型field的偏移量
    * @param expect 希望field中存在的值
    * @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值
    * @return 如果field的值被更改返回true
    */
    public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
    
    /**
    * unsafe.getAndAddInt
    * 
    * @param atomicInstance 需要更新的对象
    * @param valueOffSet atomicInstance中 value 的偏移量
    * @param delta 希望设置 value 的值为这个新值
    * @return 如果field的值被更改返回true
    */
    public final int getAndAddInt(Object atomicInstance, long valueOffSet, int delta) {
        int result;
        do {
            result = this.getIntVolatile(atomicInstance, valueOffSet);
        } while(!this.compareAndSwapInt(atomicInstance, valueOffSet, result, result + delta));

        return result;
    }

这里就是 CAS 的核心,它会原子性的完成以下三个操作:

语义化后的参数我们可以明白:

  1. 会去获取内存中 atomicInstance 中 value 的原始值
  2. 确认原始值没有被其它线程修改时,再执行更新 result + delta 操作,否则会一直循环,等待工作内存中的值与主内存中的值同步

多线程下的 CAS

我们再来对照着源码,走一遍过程,了解一下多线程下 CAS 是如何运作的。

    public final int getAndAddInt(Object atomicInstance, long valueOffSet, int delta) {
        int result;
        do {
            //参考 volatile 的作用,此时获取到的是内存中最新的值
            result = this.getIntVolatile(atomicInstance, valueOffSet);
            //进行CAS对比和更新的原子操作
        } while(!this.compareAndSwapInt(atomicInstance, valueOffSet, result, result + delta));

        return result;
    }

假设现在有两条线程,线程A和线程B在运行AtomicInteger 从1自增到100的过程,假设线程A 先执行。

  1. 线程A 首先执行 getIntVolatile() 方法,由于 getIntVolatile() 获取的值一定是内存中最新的值,所以值为 1。
  2. 线程A 下一步需要做CAS操作,假如此时线程A阻塞了(因为getAndAddInt() 并不是原子性的),线程B开始执行。
  3. 线程B 首先执行 getIntVolatile() 方法,获取到内存中的值同样为1。
  4. 线程B 进行 CAS 操作,从 atomicInstance 中获取到内存中 value 的值为1,与 result 的值相等,所以进行更新,更新的值为 1 + 1 = 2,此时内存中的值为2。线程B 执行结束。
  5. 此时线程A 恢复,继续执行,当进行 CAS 操作时,发现 atomicInstance 中获取到内存中 value 的值为2,而线程A中 result 的值为1,两者不相等,所以不进行更新操作,进行循环。
  6. 下一轮循环,线程A 重新执行 getIntVolatile() 方法,此时获取到的 result 值为 2。
  7. 此时线程A 执行 CAS 操作,从 atomicInstance 中获取到内存中 value 的值为2,与 result 的值相等,所以进行更新,更新的值为 2 + 1 = 3,此时内存中的值为3。线程A 执行结束。

ps:上述内容有些可能描述不清楚,还望参考以下博客进行理解。如描述有错误,还望留言,我会虚心积极改正,谢谢~

参考:
https://www.imooc.com/article/25026?block_id=tuijian_wz
https://www.cnblogs.com/sharkli/p/5623524.html
https://www.jb51.net/article/136718.htm

上一篇 下一篇

猜你喜欢

热点阅读