Java併發編程之原子性-Atomic源碼詳解

1、Atomic中存在Atmomicxxx的類,都是通過CAS來實現原子性的。

對於平時適用count++問題,count++並不是線程安全的,所以在多線程情況下,適用count++會出現得到的值並不是我們期望的值。

問題如下:

在這裏插入圖片描述

所以爲了解決此類問題我們需要用到Atomic,例如我們可以適用AtomicInteger來代替count++操作,保證線程安全。

例子如下:

/**
 * @author v_vllchen
 */

public class AtomicExample {
    private static final Logger LOGGER = LoggerFactory.getLogger(AtomicExample.class);
    // 請求總數
    public static int clientTotal = 5000;

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

    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) {
                    e.printStackTrace();
                    LOGGER.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("count:{}"+count.get());
        LOGGER.info("count:{}", count.get());
    }

    private static void add() {
        count.incrementAndGet();
    }
}

代碼來自https://blog.csdn.net/qq_34871626/article/details/81411815

在該代碼中我們可以看到我們請求的總數是5000,併發執行的線程數爲200,通過累加對AtomicInteger進行累加可以看到最終的結果是5000;

在這裏插入圖片描述

以上我們可以看到適用Atomic的確解決了count++的線程安全問題,但是底層如何通過CAS來實現的這樣一個過程呢,還是要通過源碼來解釋

1. 在add()方法中有一個incrementAndGet方法,我們可以進入這個源碼內部

在這裏插入圖片描述

看到它調用了unsafe類下面的getAndAddInt方法,這個方法中有三個參數,第一個參數是對象本身、第二個參數是valueOffset用來記錄當前value在內存中的編譯地址,第三個參數爲常量

2. 繼續深入查看unsafe中的getAndAddInt方法,

在這裏插入圖片描述

從第一個步中我們知道了var1是對象本身,var2是value在內存中的編譯地址,var4是一個常數。 該方法中有兩個方法:

1、getIntVolatile(),該方法通過var1以及var2來找到內存中的值,該方法是一個native方法,並不是Java實現的。

2、compareAndSwapInt(),在方法中通過while語句來實現compareAndSwapInt()方法,該方法是用來比較var1與getIntVolatile()底層返回的value,如果相同則把var5更新爲var5+var4,如果不相同,則循環通過getIntVolatile()獲取底層的value值,直到當前的var1與底層的返回的value相同才做更新。

compareAndSwapInt()方法的核心就是通常所說的CAS。

以上就是Atomic的實現思想

但是對於CAS來說會遇到一個經常性的問題就是ABA問題(線程1將值A改成B,接着又改回了A,其它線程通過與值做比較時發現值沒有改變,則直接進行更改,實際上值已經被更改了)。

所以對於這種問題,在Atomic中通過AtomicStampReference類來解決ABA問題。ABA問題的解決辦法就是在線程每次去更新的時候將版本號加一,這樣其它線程通過版本號檢測該值是否被更新過。

public class AtomicStampedReference<V> {
 
    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
 
   ... 此處省略多個方法 ....
 
   public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
}

通過源碼中我們看到compareAndSet方法中通過比較stamp這個值,來確定線程的值是否被更新。

結語:

算是對以前面試知識點的查漏補缺吧,有次面試問到這個Atomic,自己不懂,但是自己本着不能不提供解決方案的思想,在多線程問題上提出了使用ConcurrentHashMap來實現面試官提出的問題,但是得到了一個殺雞焉用牛刀的評價,對於自己缺失的知識還是要及時做補充。

我是一個走在學習路上、膜拜大佬的程序員!

在這裏插入圖片描述

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