Java併發編程——Atomic基本類型

一. Atomic基本類型

1.基本數據類型

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean

2.常用方法

  • int get() // 獲取當前值
  • int getAndSet(int value) // 獲取當前值並設置成指定值等價於普通變量的i = value
  • int getAndIncrement() // 自增 ,等價於普通變量的i++
  • int getAndDecrement() // 自減,等價於普通變量的i–
  • int getAndAdd(int Value) // 加上預期值,等價於普通變量的i = i + value
  • boolean compareAndSet(int expect, int update) //如果該對象等於期望值,把它更新成update的值,等價於普通變量的`if (i == expect) {i = update}

二. AtomicInteger

  • AtomicInteger 底層採用的是CAS原理(CompareAndSwap)。AtomicInteger 類型的對象,之所以在對AtomicInteger對象操作時,能保證線程安全,是因爲在進行操作時,會將當前對象的值與內存中變量的值進行比較,如果是一致的,則進行變量操作,否則繼續去內存中獲取變量的值,直至當前值,與內存中的值是一致的。
  • AtomicInteger 對象,可以通過調用incrementAndGet()方法和getAndIncrement()方法進行值加1的操作,區別在於:incrementAndGet()方法先執行加1操作,然後獲取值;getAndIncrement()方法是先獲取值然後執行加1的操作;

三. AtomicInteger 對象使用示例:

1.非線程安全的使用示例:

@Slf4j
public class CountExample1 {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    public static int count = 0;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        count++;
    }
}

上述代碼每次執行過程中,輸出的count值都有可能會不相同,而且小於5000,這是因爲count++操作時,會發生併發問題;

2. 使用AtomicInteger 實現線程安全的使用示例:

@Slf4j
public class AtomicExample1 {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

	//創建AtomicInteger對象並初始化
    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }

    private static void add() {
        count.incrementAndGet(); //先增加後獲取
//        count.getAndIncrement(); //先獲取後增加
    }
}

3. AtomicInteger線程安全底層實現

上述AtomicInteger對象執行incrementAndGet()方法時,底層方法是:

    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

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

其中compareAndSwapInt()方法是CAS機制中的一種,它的源碼如下:

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

由於compareAndSwapInt()方法是被native關鍵字修飾,說明這個方法是原生函數,也就是這個方法是用C/C++語言實現的,並且被編譯成了DLL,由java去調用。這些函數的實現體在DLL中,JDK的源代碼中並不包含,我們是看不到的。

getAndAddInt()方法執行過程如下這篇文章所示:getAndAddInt()執行過程


二、原子類的ABA問題

原因:

  • 所謂ABA就是一個線程在CAS更新變量的時候,該變量已經被別的線程先從A該成了B,再從B該回了A,此時線程還是會CAS成功,因爲它認爲期望值並沒有變

解決辦法:

  • 解決辦法是增加版本號,每次修改版本號加1
  • 具體解決方法:使用版本號功能的類爲AtomicStampedReference

Java併發編程學習系列

如有幫助,煩請點贊收藏一下啦 (◕ᴗ◕✿)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章