高級併發編程系列十三(一文搞懂原子類)

1.考考你

你們週末好!又到咱們一週分享的時候了。相信做爲資深程序員的你,對於AtomicInteger這樣的類,即以Atomic開始的類必定不會感到陌生。咱們在翻看不少框架源碼、或者第三方組件都會常常看到它們,如影隨形。java

那麼問題來了,像Atomicxxx這樣的類,究竟是什麼意思呢?從字面意思比較好理解,Atomic即原子性,那麼Atomicxxx即原子類。講到這裏,你必定還記得咱們說過線程安全的三個基本要素,咱們一塊兒來回顧一下:可見性、原子性、有序性。原子類的原子性,講的就是這個原子性,因而你能夠先記住一個結論:原子類,它是線程安全的類程序員

到這裏有朋友可能會提出質疑:你說線程安全,就線程安全嗎?我不服,你沒有講清楚。我不聽,我不聽......好吧,看官們莫急,且聽我一步一步分析,娓娓道來,話說......面試

#考考你:
1.你真的理解原子類的核心思想嗎
2.你在你的項目中,有直接用到過原子類嗎

2.案例

2.1.自增操做案例

2.1.1.普通變量版本

案例描述:編程

  • 定義一個普通的int型變量value,初始值爲:0數組

  • 開啓兩個線程thread_1,thread_2並行執行value++操做安全

  • 每一個線程執行 5000次,預期執行結果: 2 * 5000 = 10000次bash

  • 經過觀察最終執行結果,是否等於預期10000次併發

  • 結果不相等,說明線程不安全,緣由是:value++操做不是一個原子性操做框架

package com.anan.edu.common.newthread.atomic;

/**
 * 普通 int變量 ++ 操做,非原子性,線程不安全
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2020/11/29 8:27
 */
public class CommonIntDemo {

    /**
     * 普通成員變量
     */
    private int value = 0;

    public  void addValue(){
        value++;
    }

    public static void main(String[] args) throws InterruptedException {
        // 1.建立CommonIntDemo對象
        CommonIntDemo demo = new CommonIntDemo();

        // 2.建立2兩個線程,每一個線程調用方法addValue 5000次
        // 預期value值結果等於:2 * 5000 = 10000
        int loopEnd = 5000;
        Thread thread_1 = new Thread(() -> {
            for(int i = 0; i < loopEnd; i++){
                demo.addValue();
            }
        }, "thread_1");

        Thread thread_2 = new Thread(() -> {
            for(int i = 0; i < loopEnd; i++){
                demo.addValue();
            }
        }, "thread_2");

        // 3.啓動執行線程
        thread_1.start();
        thread_2.start();

        // 4.主線程等待子線程執行完成,打印value值
        thread_1.join();
        thread_2.join();

        System.out.println("int型成員變量value最終結果:" + demo.value);
    }
}

執行結果分析:工具

2.1.2.AtomicInteger版本

案例描述:

  • 定義一個AtomicInteger變量value,初始值爲:0

  • 開啓兩個線程thread_1,thread_2並行執行value.incrementAndGet()操做

  • 每一個線程執行 5000次,預期執行結果: 2 * 5000 = 10000次

  • 經過觀察最終執行結果,是否等於預期10000次

  • 結果相等,說明線程安全,緣由是:原子類同時知足了可見性、與原子性

package com.anan.edu.common.newthread.atomic;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 原子類AtomicInteger,實現自增操做,線程安全
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2020/11/29 8:27
 */
public class AtomicIntegerDemo {

    /**
     * AtomicInteger成員變量
     */
    private AtomicInteger value = new AtomicInteger(0);

    public  void addValue(){
        value.incrementAndGet();
    }

    public static void main(String[] args) throws InterruptedException {
        // 1.建立AtomicIntegerDemo對象
        AtomicIntegerDemo demo = new AtomicIntegerDemo();

        // 2.建立2兩個線程,每一個線程調用方法addValue 5000次
        // 預期value值結果等於:2 * 5000 = 10000
        int loopEnd = 5000;
        Thread thread_1 = new Thread(() -> {
            for(int i = 0; i < loopEnd; i++){
                demo.addValue();
            }
        }, "thread_1");

        Thread thread_2 = new Thread(() -> {
            for(int i = 0; i < loopEnd; i++){
                demo.addValue();
            }
        }, "thread_2");

        // 3.啓動執行線程
        thread_1.start();
        thread_2.start();

        // 4.主線程等待子線程執行完成,打印value值
        thread_1.join();
        thread_2.join();

        System.out.println("AtomicInteger型成員變量value最終結果:" + demo.value);
    }
}

執行結果分析:

2.2.原子類底層原理分析

2.2.1.再次分析線程安全核心思想

經過比較普通類型int型變量自增操做,與原子型AtomicInteger型變量自增操做。咱們看到應用層代碼幾乎沒有差別,僅僅是經過AtomicInteger替換int實現自增操做,即保證了線程安全。那麼AtomicInteger它是如何作到的呢?

要分析清楚AtomicInteger底層原理,還須要回到咱們說過的線程安全基本要素:可見性、原子性、有序性。就是說無論經過什麼手段,要實現線程安全,必定要知足這三個基本要素,換句話說,知足了三個基本要素,也即實現了線程安全。

那麼咱們就從這三個要素開始分析。首先看最容易理解的有序性,你還記得什麼是有序性嗎?它是說線程內有序,線程之間無序。有序性比較好理解,咱們就不過多解釋了。

再來看可見性,一樣你還記得什麼是可見性嗎?咱們知道jmm內存模型,每一個線程都有本身的私人空間(工做內存),全部線程共享公共空間(主內存)。那麼若是要保證某個變量在線程間的可見性,即當線程A操做該變量後,須要同步將變量值從私人空間同步到公共空間:工做內存--->主內存;同理其它線程在操做變量前,須要從公共空間將變量值同步到私人空間:主內存--->工做內存。java編程語法上給咱們提供了一個關鍵字:volatile。用於實現可見性。你可能還須要下面這個圖:

最後再來看原子性,原子性你應該還記得,咱們上一篇:高級併發編程系列十二(一文搞懂cas)剛剛分享過。cas本質上是不到黃河心不死,什麼意思呢?便是不釋放cpu,循環操做,直到操做成功爲止。咱們是這麼解釋的,你也應該還記得對吧。並且咱們還說過對於cas,它的操做原理是三個值:內存值A、指望值B、更新值C。每次操做都會比較內存值A,是否等於指望值B、若是等於則將內存值更新成值C,操做成功;若是內存值A,不等於指望值B,則操做失敗,進行下一次循環操做。你可能還須要下面這個圖:

好了到這裏,咱們能夠一塊兒來看AtomicInteger的源碼了。看看是否知足咱們說的可見性、原子性。進一步分析清楚AtomicInteger類線程安全的實現原理。下面咱們經過截圖+文字描述的方式,方便你理解。

2.2.2.AtomicInteger類聲明

先來看AtomicInteger類的聲明,這一塊對於不熟悉的朋友可能比較難看懂,咱們先截圖看一下。

2.2.3.方法incrementAndGet分析

經過類聲明部分源碼,咱們看到線程安全的可見性,經過volatile關鍵字修飾value成員變量,已經有了保障。那麼原子性,又是如何保障的呢?答案是經過Unsafe工具類,進行cas操做來保障的。看圖:

2.3.juc原子類分類

相信經過上面的分析,你已經理解了原子類線程安全的底層實現原理,若是你理解起來稍微還有點難度,我建議你多看兩遍。對於一個程序員來講,咱們不該該只會用用框架,底層思想和原理纔是內功。

那麼關於原子類的底層分析,咱們暫時放一放,下面咱們一塊兒來看一下juc包中提供的常見原子能力工具類。它們每個的底層原理,都在上面分析過了,我就再也不逐一分析了,只是簡單的列舉出來,若是你感興趣的話,能夠找一兩個按照我上面的分析思路,本身分析一下,應該會有意想不到的驚喜!

  • 基本原子類,表明:AtomicInteger、AtomicLong

  • 數組原子類,表明:AtomicIntegerArray、AtomicLongArray

  • 引用原子類,表明:AtomicReference<V>。關於引用原子類,稍微加一句:它能夠把一個普通對象,包裝成具備原子能力的對象

  • 提供升級能力原子類,表明:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater

  • 累加器原子類,表明:LongAdder。關於累加器,稍微多加一句:它是jdk1.8開始後新加入的小夥伴,性能比起AtomicLong來講槓槓的。據說目前是個面試熱點話題喲!值得去研究一下

相關文章
相關標籤/搜索