Java多線程進階(十三)—— J.U.C之atomic框架:AtomicInteger

7.jpeg

本文首發於一世流雲的專欄: https://segmentfault.com/blog...

1、AtomicInteger簡介

AtomicInteger,應該是atomic框架中用得最多的原子類了。顧名思義,AtomicInteger是Integer類型的線程安全原子類,能夠在應用程序中以原子的方式更新int值。java

1. 建立AtomicInteger對象

先來看下AtomicInteger對象的建立。segmentfault

AtomicInteger提供了兩個構造器,使用默認構造器時,內部int類型的value值爲0:
AtomicInteger atomicInt = new AtomicInteger();api

AtomicInteger類的內部並不複雜,全部的操做都針對內部的int值——value,並經過Unsafe類來實現線程安全的CAS操做:
clipboard.png緩存

2. AtomicInteger的使用

來看下面這個示例程序:安全

public class Main {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger ai = new AtomicInteger();

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(new Accumlator(ai), "thread-" + i);
            list.add(t);
            t.start();
        }

        for (Thread t : list) {
            t.join();
        }

        System.out.println(ai.get());
    }

    static class Accumlator implements Runnable {
        private AtomicInteger ai;

        Accumlator(AtomicInteger ai) {
            this.ai = ai;
        }

        @Override
        public void run() {
            for (int i = 0, len = 1000; i < len; i++) {
                ai.incrementAndGet();
            }
        }
    }
}

上述代碼使用了AtomicInteger的incrementAndGet方法,以原子的操做對int值進行自增,該段程序執行的最終結果爲10000(10個線程,每一個線程對AtomicInteger增長1000),若是不使用AtomicInteger,使用原始的int或Integer,最終結果值可能會小於10000(併發時讀到了過期的數據或存在值覆蓋的問題)。併發

咱們來看下incrementAndGet內部:
clipboard.pngoracle

內部調用了Unsafe類的getAndAddInt方法,以原子方式將value值增長1,而後返回增長前的原始值。框架

注意,上述是JDK1.8的實現,在JDK1.8以前,上述方法採用了自旋+CAS操做的方式:ide

public final int getAndIncrement() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return current;
    }
}

3. AtomicInteger的特殊方法說明

AtomicInteger中有一個比較特殊的方法——lazySet
clipboard.png性能

lazySet方法是set方法的不可見版本。什麼意思呢?

咱們知道經過volatile修飾的變量,能夠保證在多處理器環境下的「可見性」。也就是說當一個線程修改一個共享變量時,其它線程能當即讀到這個修改的值。volatile的實現最終是加了內存屏障:

  1. 保證寫volatile變量會強制把CPU寫緩存區的數據刷新到內存
  2. 讀volatile變量時,使緩存失效,強制從內存中讀取最新的值
  3. 因爲內存屏障的存在,volatile變量還能阻止重排序

lazySet內部調用了Unsafe類的putOrderedInt方法,經過該方法對共享變量值的改變,不必定能被其餘線程當即看到。也就是說以普通變量的操做方式來寫變量。

爲何會有這種奇怪方法?什麼狀況下須要使用lazySet呢?

考慮下面這樣一個場景:

private AtomicInteger ai = new AtomicInteger();
lock.lock();
try
{
    // ai.set(1);
}
finally
{
    lock.unlock();
}

因爲鎖的存在:

  • lock()方法獲取鎖時,和volatile變量的讀操做同樣,會強制使CPU緩存失效,強制從內存讀取變量。
  • unlock()方法釋放鎖時,和volatile變量的寫操做同樣,會強制刷新CPU寫緩衝區,把緩存數據寫到主內存

因此,上述ai.set(1)能夠用ai.lazySet(1)方法替換:

由鎖來保證共享變量的可見性,以設置普通變量的方式來修改共享變量,減小沒必要要的內存屏障,從而提升程序執行的效率。

2、類/接口說明

類聲明

clipboard.png

構造器

clipboard.png

接口聲明

方法聲明 描述
int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) 使用IntBinaryOperator 對當前值和x進行計算,並更新當前值,返回計算後的新值
int addAndGet(int delta) 以原子方式將給定值與當前值相加,返回相加後的新值
boolean compareAndSet(int expect, int update) 若是當前值 == expect,則以原子方式將該值設置爲給定的更新值(update)
int decrementAndGet() 以原子方式將當前值減 1,返回新值
int get() 獲取當前值
int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) 使用IntBinaryOperator 對當前值和x進行計算,並更新當前值,返回計算前的舊值
int getAndAdd(int delta) 以原子方式將給定值與當前值相加,返回舊值
int getAndDecrement() 以原子方式將當前值減 1,返回舊值
int getAndIncrement() 以原子方式將當前值加 1,返回舊值
int getAndSet(int newValue) 以原子方式設置爲給定值,並返回舊值
int getAndUpdate(IntUnaryOperator updateFunction) 使用IntBinaryOperator 對當前值進行計算,並更新當前值,返回計算前的舊值
int incrementAndGet() 以原子方式將當前值加 1,返回新值
void lazySet(int newValue) 設置爲給定值,但不保證值的改變被其餘線程當即看到
void set(int newValue) 設置爲給定值
int updateAndGet(IntUnaryOperator updateFunction) 使用IntBinaryOperator 對當前值進行計算,並更新當前值,返回計算後的新值
boolean weakCompareAndSet(int expect, int update) weakCompareAndSet沒法保證除操做目標外的其餘變量的執行順序( 編譯器和處理器爲了優化程序性能而對指令序列進行從新排序 ),同時也沒法保證這些變量的可見性。

3、其它原子類

AtomicInteger相似的原子類還有AtomicBooleanAtomicLong,底層都是經過Unsafe類作CAS操做,來原子的更新狀態值。能夠參考Oracle官方文檔:https://docs.oracle.com/javas...,再也不贅述。

相關文章
相關標籤/搜索