Java 多線程 : JUC 併發工具原理
首先分享之前的所有文章 , 歡迎點贊收藏轉發三連下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 備份 : 👉 gitee.com/antblack/ca…
一 . 前言
趁着有空 , 趕緊把之前欠的債還上 . 這是多線程一階段計劃的最後一篇 , 後續多線程會轉入修訂和深入階段 . 徹底喫透多線程.
二. 工具介紹
之前說 AQS 的時候曾經提到過這幾個類 , 這幾個類有一些各自的特點 , 很符合特定的場景 , 之前在生產上用的還挺舒服.
我們一般使用的併發工具有四種 :
CyclicBarrier : 放學一起走
- 允許一組線程互相等待,直到到達某個公共屏障點 (common barrier point)
- 讓一組線程到達一個屏障時被阻塞,直到最後一個線程到達屏障時,屏障纔會開門,所有被屏障攔截的線程纔會繼續幹活
CountDownLatch : 等人到齊了就觸發
- 在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待
- 用給定的計數 初始化 CountDownLatch。
- 由於調用了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。
- 之後,會釋放所有等待的線程,await 的所有後續調用都將立即返回。這種現象只出現一次——計數無法被重置。
- CountDownLatch是通過一個計數器來實現的,當我們在new 一個CountDownLatch對象的時候需要帶入該計數器值,該值就表示了線程的數量。
- 每當一個線程完成自己的任務後,計數器的值就會減1。當計數器的值變爲0時,就表示所有的線程均已經完成了任務
Semaphore
- 信號量Semaphore是一個控制訪問多個共享資源的計數器,和CountDownLatch一樣,其本質上是一個“共享鎖”。
Exchanger
- 可以在對中對元素進行配對和交換的線程的同步點
- 每個線程將條目上的某個方法呈現給 exchange 方法,與夥伴線程進行匹配,並且在返回時接收其夥伴的對象 , Exchanger 可能被視爲 SynchronousQueue 的雙向形式
三 .原理解析
3 .1 CyclicBarrier
作用 :
它允許一組線程互相等待,直到到達某個公共屏障點 (Common Barrier Point)。在涉及一組固定大小的線程的程序中,這些線程必須不時地互相等待,此時 CyclicBarrier 很有用。因爲該 Barrier 在釋放等待線程後可以重用,所以稱它爲循環( Cyclic ) 的 屏障( Barrier ) 。
內部原理 :
內部使用重入鎖ReentrantLock 和 Condition
構造函數 :
-
CyclicBarrier(int parties):
- 創建一個新的 CyclicBarrier,它將在給定數量的參與者(線程)處於等待狀態時啓動,
- 但它不會在啓動 barrier 時執行預定義的操作。
-
CyclicBarrier(int parties, Runnable barrierAction) :
- 創建一個新的 CyclicBarrier,它將在給定數量的參與者(線程)處於等待狀態時啓動,
- 並在啓動 barrier 時執行給定的屏障操作,該操作由最後一個進入 barrier 的線程執行。
使用變量 :
- parties 變量 : 表示攔截線程的總數量。
- count 變量 : 表示攔截線程的剩餘需要數量。
- barrierAction 變量 : 爲 CyclicBarrier 接收的 Runnable 命令,用於在線程到達屏障時,優先執行 barrierAction ,用於處理更加複雜的業務場景。
- generation 變量 : 表示 CyclicBarrier 的更新換代
// 常用方法 :
M- await : 等待狀態
M- await(long timeout, TimeUnit unit) : 等待超時
M- dowait
- 該方法第一步會試着獲取鎖
- 如果分代已經損壞,拋出異常
- 如果線程中斷,終止CyclicBarrier
- 進來線程 ,--count
- count == 0 表示所有線程均已到位,觸發Runnable任務
- 喚醒所有等待線程,並更新generation
> 跳出等待狀態的方法
- 最後一個線程到達,即index == 0
- 超出了指定時間(超時等待)
- 其他的某個線程中斷當前線程
- 其他的某個線程中斷另一個等待的線程
- 其他的某個線程在等待barrier超時
- 其他的某個線程在此barrier調用reset()方法。reset()方法用於將屏障重置爲初始狀態。
SC- Generation : 描述了 CyclicBarrier 的更新換代。
- 在CyclicBarrier中,同一批線程屬於同一代。
- 當有 parties 個線程全部到達 barrier 時,generation 就會被更新換代。
- 其中 broken 屬性,標識該當前 CyclicBarrier 是否已經處於中斷狀態
M- breakBarrier : 終止所有的線程
M- nextGeneration : 更新換代操作
- 1. 喚醒所有線程。
- 2. 重置 count 。
- 3. 重置 generation 。
M- reset : 重置 barrier 到初始化狀態
M- getNumberWaiting : 獲得等待的線程數
M- 判斷 CyclicBarrier 是否處於中斷
複製代碼
使用案例 :
問題補充 :
// 問題一 : 攔截的核心
1. 傳入總得 Count 數
2. 每次進來都會 --count , 同時判斷 count ==0
3. 如果不爲 0 ,當前線程就會阻塞
// 問題二 : 涉及源碼
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
複製代碼
3.2 CountDownLatch
在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待
用給定的計數 初始化 CountDownLatch。由於調用了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。之後,會釋放所有等待的線程,await 的所有後續調用都將立即返回。這種現象只出現一次, 計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier。
CountDownLatch是通過一個計數器來實現的,當我們在new 一個CountDownLatch對象的時候需要帶入該計數器值,該值就表示了線程的數量。每當一個線程完成自己的任務後,計數器的值就會減1。當計數器的值變爲0時,就表示所有的線程均已經完成了任務
// 內部主要方法
> CountDownLatch內部依賴Sync實現,而Sync繼承AQS
> sync :
: tryAcquireShared 獲取同步狀態
: tryReleaseShared 釋放同步狀態
> await() :
使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷
: sync.acquireSharedInterruptibly(1);
: 內部使用AQS的acquireSharedInterruptibly(int arg)
> getState()
: 獲取同步狀態,其值等於計數器的值
: 從這裏我們可以看到如果計數器值不等於0,則會調用doAcquireSharedInterruptibly(int arg)
> doAcquireSharedInterruptibly
: 自旋方法會嘗試一直去獲取同步狀態
> countDown
: CountDownLatch提供countDown() 方法遞減鎖存器的計數,如果計數到達零,則釋放所有等待的線程
: 內部調用AQS的releaseShared(int arg)方法來釋放共享鎖同步狀態
: tryReleaseShared(int arg)方法被CountDownLatch的內部類Sync重寫
複製代碼
參考案例
總結
CountDownLatch 內部通過共享鎖實現。在創建CountDownLatch實例時,需要傳遞一個int型的參數:count,該參數爲計數器的初始值,也可以理解爲該共享鎖可以獲取的總次數。
當某個線程調用await()方法,程序首先判斷count的值是否爲0,如果不會0的話則會一直等待直到爲0爲止 (PS : 可以多個線程都調用 await)
當其他線程調用countDown()方法時,則執行釋放共享鎖狀態,使count值 – 1 (PS :countDown 並不會阻塞)
當在創建CountDownLatch時初始化的count參數,必須要有count線程調用countDown方法纔會使計數器count等於0,鎖纔會釋放,前面等待的線程纔會繼續運行。注意CountDownLatch不能回滾重置
3 .3 Semaphore
基礎點
信號量Semaphore是一個控制訪問多個共享資源的計數器,和CountDownLatch一樣,其本質上是一個“共享鎖”。
從概念上講,信號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然後再獲取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。
Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的線程數目
當一個線程想要訪問某個共享資源時,它必須要先獲取Semaphore,當Semaphore >0時,獲取該資源並使Semaphore – 1。如果Semaphore值 = 0,則表示全部的共享資源已經被其他線程全部佔用,線程必須要等待其他線程釋放資源。當線程釋放資源時,Semaphore則+1
實現細節
Semaphore提供了兩個構造函數:
- Semaphore(int permits) :創建具有給定的許可數和非公平的公平設置的 Semaphore。
- Semaphore(int permits, boolean fair) :創建具有給定的許可數和給定的公平設置的 Semaphore。
Semaphore默認選擇非公平鎖。
當信號量Semaphore = 1 時,它可以當作互斥鎖使用。其中0、1就相當於它的狀態,當=1時表示其他線程可以獲取,當=0時,排他,即其他線程必須要等待。
//------ 信號量獲取
> acquire()方法來獲取一個許可
: 內部調用AQS的acquireSharedInterruptibly(int arg),該方法以共享模式獲取同步狀態
> 公平
: 判斷該線程是否位於CLH隊列的列頭
: 獲取當前的信號量許可
: 設置“獲得acquires個信號量許可之後,剩餘的信號量許可數”
: CAS設置信號量
> 非公平
: 不需要判斷當前線程是否位於CLH同步隊列列頭
複製代碼
3 .4 Exchanger
可以在對中對元素進行配對和交換的線程的同步點
每個線程將條目上的某個方法呈現給 exchange 方法,與夥伴線程進行匹配,並且在返回時接收其夥伴的對象 , Exchanger 可能被視爲 SynchronousQueue 的雙向形式
Exchanger,它允許在併發任務之間交換數據 : 當兩個線程都到達同步點時,他們交換數據結構,因此第一個線程的數據結構進入到第二個線程中,第二個線程的數據結構進入到第一個線程中
TODO : Exchanger 的源代碼比較繞 ,而且這個組件使用場景並不多 , 所以先留個坑 , 以後項目上真的有場景了再實際上分析一下
3.5 併發工具使用
補充 :
# CountDownLatch 和 CyclicBarrier 如何理解 ?
- CyclicBarrier : 小學生去郊遊 , 老師下車時統計人數 ,人數到齊了才能一起參觀
- CountDownLatch : 幼兒園老師送孩子(ChildThread)放學 , 走一個記一個數 ,當所有的學生放學後 , 老師(BossThread)下班
// 核心解釋 :
CyclicBarrier 就是一堵牆 , 人數到了所有線程才能一起越過牆
CountDownLatch 只是一個計數器 , 數目到了主線程才能執行
// 其他要點 :
CyclicBarrier 可以重置計數 , CountDownLatch 不可以
複製代碼
總結
終於補上了最後一塊板 , 後面來真正的深入多線程看看吧 , 爭取早日成爲多線程大師段位
更新記錄
- 20210915 : 優化佈局