併發類 AtomicInteger 使用入門及源碼詳解

AtomicInterger 介紹

能夠原子性更新的 Integer 值,固然這個類並不能徹底替代 Integer 對象。html

AtomicInterger

使用

使用起來仍是很方便的。java

好比說咱們定義一個計數器,使用 AtomicInteger 能夠同時兼顧性能與併發安全。程序員

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author binbin.hou
 * @since 1.0.0
 */
public class Counter {

    private AtomicInteger c = new AtomicInteger(0);

    /**
     * 遞增
     */
    public void increment() {
        c.getAndIncrement();
    }

    /**
     * 獲取值
     * @return 值
     */
    public int value() {
        return c.get();
    }

    public static void main(String[] args) throws InterruptedException {
        final Counter counter = new Counter();

        //1000 threads
        for(int i = 0; i < 100 ; i++) {
            new Thread(new Runnable() {
                public void run() {
                    counter.increment();
                }
            }).start();
        }
        Thread.sleep(1000);
        System.out.println("Final number (should be 100): " + counter.value());
    }

}

日誌輸出:緩存

Final number (should be 100): 100

AtomicInteger 源碼

可惡!這個類使用起來居然這麼方便。安全

那麼李大狗是如何實現的呢?併發

AtomicInteger 源碼

類定義

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

繼承自 Number 類,實現了序列化接口。app

內部屬性

這裏初始化了 unsafe 變量,用於後面使用 CAS 作變量更新。jvm

// 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;
  • objectFieldOffset 方法

這是一個 native 方法,換言之就是直接調用的操做系統,獲取到 value 變量的內存偏移量信息。ide

public native long objectFieldOffset(Field var1);

構造器

平淡無奇的構造器,用來初始化 value。性能

固然也能夠不指定,不指定的時候默認值是什麼呢?

我想各位讀者確定都清楚,不清楚的能夠留言區懺悔一下。

/**
 * Creates a new AtomicInteger with the given initial value.
 *
 * @param initialValue the initial value
 */
public AtomicInteger(int initialValue) {
    value = initialValue;
}

/**
 * Creates a new AtomicInteger with initial value {@code 0}.
 */
public AtomicInteger() {
}

基本的方法

/**
 * Gets the current value.
 *
 * @return the current 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;
}

/**
 * Returns the String representation of the current value.
 * @return the String representation of the current value
 */
public String toString() {
    return Integer.toString(get());
}
/**
 * Returns the value of this {@code AtomicInteger} as an {@code int}.
 */
public int intValue() {
    return get();
}
/**
 * Returns the value of this {@code AtomicInteger} as a {@code long}
 * after a widening primitive conversion.
 * @jls 5.1.2 Widening Primitive Conversions
 */
public long longValue() {
    return (long)get();
}
/**
 * Returns the value of this {@code AtomicInteger} as a {@code float}
 * after a widening primitive conversion.
 * @jls 5.1.2 Widening Primitive Conversions
 */
public float floatValue() {
    return (float)get();
}
/**
 * Returns the value of this {@code AtomicInteger} as a {@code double}
 * after a widening primitive conversion.
 * @jls 5.1.2 Widening Primitive Conversions
 */
public double doubleValue() {
    return (double)get();
}

這兩個方法和普通類中的 getter/setter等並無區別,此處不作過多解釋。

基於 unsafe 的方法

爲何 AtomicInteger 能保持原子性呢?

咱們一塊兒來看一下是如何基於 Unsafe 實現原子性的?

lazySet 惰性設置

/**
 * 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);
}

最終會把值設置爲給定的值。這是什麼意思?我直接懵了。

其實這個是相對的,咱們前面說過,volatile 修飾的變量,修改後能夠保證線程間的可見性。可是這個方法,修改後並不保證線程間的可見性

這和之前在網上看到的可不同,不是說好的 AtomicXXX 都是基於 volatile+cas 實現的嗎?這裏爲何要反其道而行之呢?

實際上是爲了性能,lazySet 有本身的應用場景。

高級程序員都知道 volatile 能夠保證變量在線程間的可見性,可是這裏再問一句,不使用 volatile 修飾就沒法保證可見性了嗎?

事實上,這裏徹底能夠不用 volatile 變量來修飾這些共享狀態,

  1. 由於訪問共享狀態以前先要得到鎖, Lock.lock()方法可以得到鎖,而得到鎖的操做和volatile變量的讀操做同樣,會強制使CPU緩存失效,強制從內存讀取變量。

  2. Lock.unlock()方法釋放鎖時,和寫volatile變量同樣,會強制刷新CPU寫緩衝區,把緩存數據寫到主內存

底層也是經過加內存屏障實現的。

而lazySet()優化原理,就是在不須要讓共享變量的修改馬上讓其餘線程可見的時候,以設置普通變量的方式來修改共享狀態,能夠減小沒必要要的內存屏障,從而提升程序執行的效率。

這個討論能夠參考 stackoverflow 的問題 AtomicInteger lazySet vs. set

原子性設置值

/**
 * Atomically sets to the given value and returns the old value.
 *
 * @param newValue the new value
 * @return the previous value
 */
public final int getAndSet(int newValue) {
    return unsafe.getAndSetInt(this, valueOffset, newValue);
}

這個方法實現以下:

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

實際上就是咱們常說的 volatile + CAS 實現。

compareAndSet 比較而且設置

jdk 將 CAS 這個方法暴露給了開發者,不過作了一層封裝,讓 unsafe 類對使用者不可見。

compareAndSwapInt 這個方法是一個 native 方法,此處不作深刻。其餘的方法不少都大同小異,因此咱們再也不贅述。

ps: 很煩,native 方法直接看源碼就會變得很麻煩,之後有時間研究下 openJdk 之類的。

/**
 * 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(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

weakCompareAndSet

這個方法我以爲也頗有趣,弱比較?拿泥搜來。

/**
 * Atomically sets the value to the given updated value
 * if the current value {@code ==} the expected value.
 *
 * <p><a href="package-summary.html#weakCompareAndSet">May fail
 * spuriously and does not provide ordering guarantees</a>, so is
 * only rarely an appropriate alternative to {@code compareAndSet}.
 *
 * @param expect the expected value
 * @param update the new value
 * @return {@code true} if successful
 */
public final boolean weakCompareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

咱們發現這兩個方法在 jdk1.8 中其實是沒有差別的。

底層調用的都是同一個方法:

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

那區別是什麼呢?

因而我就去查了一下,JDK1.9之前,二者底層實現是同樣的,並無嚴格區分。

JDK 1.9提供了Variable Handles的API,主要是用來取代java.util.concurrent.atomic包以及sun.misc.Unsafe類的功能。

Variable Handles須要依賴jvm的加強及編譯器的協助,即須要依賴java語言規範及jvm規範的升級。

VarHandle中compareAndSet和compareAndSet的定義以下:

(1)compareAndSet(Object... args)

Atomically sets the value of a variable to the newValue with the memory semantics of set(java.lang.Object...) if the variable's current value, referred to as the witness value, == the expectedValue, as accessed with the memory semantics of getAcquire(java.lang.Object...).

(2)weakCompareAndSet(Object... args)

Possibly atomically sets the value of a variable to the newValue with the memory semantics of setVolatile(java.lang.Object...) if the variable's current value, referred to as the witness value, == the expectedValue, as accessed with the memory semantics of getVolatile(java.lang.Object...).

weakCompareAndSet的描述多了一個單詞Possibly,可能的。

weakCompareAndSet有可能不是原子性的去更新值,這取決於虛擬機的實現。

@HotSpotIntrinsicCandidate 標註的方法,在HotSpot中都有一套高效的實現,該高效實現基於CPU指令,運行時,HotSpot維護的高效實現會替代JDK的源碼實現,從而得到更高的效率。

也就是說HotSpot可能會手動實現這個方法。

@PolymorphicSignature
@HotSpotIntrinsicCandidate
public final native boolean compareAndSet(Object... var1);

@PolymorphicSignature
@HotSpotIntrinsicCandidate
public final native boolean weakCompareAndSet(Object... var1);

其實這個方法和上面的 lazySet 有殊途同歸之妙。

小結

咱們對 AtomicInteger 源碼進行了初步的分析,底層也確實是依賴 volatile+CAS 實現。

不過發現了兩個有趣的實現:weakCompareAndSet 和 lazySet。

看起來反其道而行之,實際上都是出於更高的性能考慮。

文中不少方法都是 native 實現,這讓咱們讀起來不夠盡興,說到底這個世界上本沒有高級語言,只有C語言,和對C語言的封裝

但願本文對你有幫助,若是有其餘想法的話,也能夠評論區和你們分享哦。

各位極客的點贊收藏轉發,是老馬寫做的最大動力!

深刻學習

相關文章
相關標籤/搜索