Java 源码 - java.util.concurrent.atomic.AtomicInteger

148 阅读6分钟

概述

AtomicInteger 是一个 Java concurrent 包提供的一个原子类,通过这个类可以对 Integer 进行一些原子操作。这个类的源码比较简单,主要是依赖于 sun.misc.Unsafe 提供的一些 native 方法保证操作的原子性。

继承关系

public class AtomicInteger extends Number implements java.io.Serializable 

成员属性

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

Unsafe 可以直接操控内存和线程,是一个比较危险的类。在 Java 里我们是无法直接使用这个类的,得通过反射机制获取。在这篇文章里面我们也不会深入讨论这个类,有兴趣的读者可以去看看它的 API 文档

静态代码块

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

从上图可以看出,AtomicInteger 主要有两个静态变量和一个成员变量,在初始化的时候会通过静态代码块给 valueOffset 赋值。

value 这个不用说,就是用来存储实际数值的;Unsafe 类我们之前有提到过,在这里也不做赘述了。

那么,valueOffset 这个静态变量又是用来做什么的呢?这时候我们就得看看它的赋值来源了。根据 API 文档,Unsafe 的 objectFieldOffset 方法可以获取成员属性在内存中的地址相对于对象内存地址的偏移量。说得简单点就是找到这个成员变量在内存中的地址,便于后续通过内存地址直接进行操作。
所以,valueOffset 其实就是用来定位 value,后续 Unsafe 类可以通过内存地址直接对 value 进行操作。

构造方法

    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    public AtomicInteger() {
    }

第一个方法在初始的时候就传入初始值, 而第二的方法是一个空方法,所以 AtomicInteger 在调用其默认构造方法的时候没有执行任何操作,一切都是默认值。

所以,综合静态代码和构造方法,AtomicInteger 在初始化的时候会定位其成员变量 value 的内存地址,后续通过内存地址直接对其进行操作。

基本方法

    public final int get() {
        return value;
    }

    /**
    * Sets to the given value.
    *
    * @param newValue the new value
    */    
    public final void set(int newValue) {
        value = newValue;
    }

     /**
     * Eventually sets to the given value.
     *
     * @param newValue the new value
     * @since 1.6
     */
    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }

按照javadoc的字面意思:set()会立刻修改旧值,别的线程可以立刻看到更新后的值;而lazySet不会立刻(但是最终会)修改旧值,别的线程看到新值的时间会延迟一些。

网上的查到的一些资料

As probably the last little JSR166 follow-up for Mustang, we added a "lazySet" 
method to the Atomic classes (AtomicInteger, AtomicReference, etc). 
This is a niche method that is sometimes useful when fine-tuning code using 
non-blocking data structures. The semantics are that the write is guaranteed not 
to be re-ordered with any previous write, but may be reordered with subsequent 
operations (or equivalently, might not be visible to other threads) until some 
other volatile write or synchronizing action occurs).

The main use case is for nulling out fields of nodes in non-blocking data structures
 solely for the sake of avoiding long-term garbage retention; it applies when it is 
harmless if other threads see non-null values for a while, but you'd like to ensure 
that structures are eventually GCable. In such cases, you can get better performance
 by avoiding the costs of the null volatile-write. There are a few other use cases 
along these lines for non-reference-based atomics as well, so the method is 
supported across all of the AtomicX classes.

For people who like to think of these operations in terms of machine-level barriers 
on common multiprocessors, lazySet provides a preceeding store-store barrier 
(which is either a no-op or very cheap on current platforms), but no store-load 
barrier (which is usually the expensive part of a volatile-write).

其他方法

    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

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

调用Unsafe.compareAndSwapInt()方法实现,这个方法有四个参数:

(1)操作的对象;

(2)对象中字段的偏移量;

(3)原来的值,即期望的值;

(4)要修改的值;

可以看到,这是一个native方法,底层是使用C/C++写的,主要是调用CPU的CAS指令来实现,它能够保证只有当对应偏移量处的字段值是期望值时才更新,即类似下面这样的两步操作:

if(value == expect) {
    value = newValue;
}

通过CPU的CAS指令可以保证这两步操作是一个整体,也就不会出现多线程环境中可能比较的时候value值是a,而到真正赋值的时候value值可能已经变成b了的问题。

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

    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }

getAndIncrement()方法底层是调用的Unsafe的getAndAddInt()方法,这个方法有三个参数:

(1)操作的对象;

(2)对象中字段的偏移量;

(3)要增加的值;

查看Unsafe的getAndAddInt()方法的源码,可以看到它是先获取当前的值,然后再调用compareAndSwapInt()尝试更新对应偏移量处的值,如果成功了就跳出循环,如果不成功就再重新尝试,直到成功为止,这可不就是(CAS+自旋)的乐观锁机制么^^

AtomicInteger中的其它方法几乎都是类似的,最终会调用到Unsafe的compareAndSwapInt()来保证对value值更新的原子性。

    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }

    public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }

这里,我们来看一下Unsafe的getAndAddInt方法 

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

这时候会发现它里面又调用了 getIntVolatile 和 compareAndSwapInt 方法,而这两个方法都是 native 方法,具体说明可以参照 Unsafe 的 API 文档。

getIntVolatile 的主要作用是通过对象 var1 和成员变量相对于对象的内存偏移量 var2 来直接从内存地址中获取成员变量的值,所以 var5 就是当前 AtomicInteger 的值。

而 compareAndSwapInt (简称CAS) 的主要逻辑如下:

  1. 通过对象 var1 和成员变量的内存偏移量 var2 来定位内存地址

  2. 判断当前地址的值是否等于 var5

  3. 不等于:返回 false

  4. 等于:把当前地址的值替换成 var5 + var4 并返回 true

所以,综合来说,getAndAddInt 方法的主要逻辑如下:

  1. 根据对象 var1 和内存偏移量 var2 来定位内存地址,获取当前地址值

  2. 循环通过 CAS 操作更新当前地址值直到更新成功

  3. 返回旧值

    public final int getAndUpdate(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return prev;
    }
    
    public final int updateAndGet(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return next;
    }
    
    public final int getAndAccumulate(int x,
                                      IntBinaryOperator accumulatorFunction) {
        int prev, next;
        do {
            prev = get();
            next = accumulatorFunction.applyAsInt(prev, x);
        } while (!compareAndSet(prev, next));
        return prev;
    }
    
    public final int accumulateAndGet(int x,
                                      IntBinaryOperator accumulatorFunction) {
        int prev, next;
        do {
            prev = get();
            next = accumulatorFunction.applyAsInt(prev, x);
        } while (!compareAndSet(prev, next));
        return next;
    }
    
getAndUpdate(IntUnaryOperator updateFunction):

这个方法是 Java 1.8 开始提供的,以原子方式将函数式接口的结果更新为当前值 返回原值 

updateAndGet(IntUnaryOperator updateFunction):

以原子方式将函数式接口的结果更新为当前值 返回新值

getAndAccumulate(int x, IntBinaryOperator accumulatorFunction):

以原子方式将函数式接口的结果更新为当前值 返回原值 函数式接口返回处理当前值和给定值x结果

accumulateAndGet(int x, IntBinaryOperator accumulatorFunction):

以原子方式将函数式接口的结果更新为当前值 返回新值 函数式接口返回处理当前值和给定值x结果