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來講槓槓的。據說目前是個面試熱點話題喲!值得去研究一下