三個好用的並發工具類


以前的文章中,我們介紹了太多的底層原理技術以及新概念,本篇我們輕松點,了解下 Java 並發包下、基於這些底層原理的三個框架工具類。

它們分別是:

  • 信號量 Semaphore
  • 倒計時門栓 CountDownLatch
  • 屏障 CyclicBarrier

所以,既然是工具類,那么必然是離不開特定的場景的,於是相互之間沒有誰優誰劣,只有誰更合適。

信號量 Semaphore

Semaphore 適用於什么樣的使用場景呢,我們舉個通俗的例子:

假如現在有一個停車場,里面有只十個停車位,當着十個停車位都被占用了,外面的車就不允許進入了,就必須在外面等着。出來一輛車才允許進去一輛車

這個場景不同於我們一般的並發場景,一般來說,我們的臨界資源只能允許一個線程進行訪問,其他線程都地等着。

但是,有一種場景是,臨界資源允許多個線程同時訪問,超過限定數量的外的線程得阻塞等待。

這種情境使用原始的那一套也是能實現的,但那叫「造輪子」,Java 並發框架下給我們提供了一個工具類,專門適用這種場景。

Semaphore 可以說是為上述這種場景而生的一個工具類,我們寫個 demo 實現上述邏輯:

image

執行程序之后,你會看到:

image

你看,出來一個線程才允許進去一個線程,這就是 Semaphore。

semaphore 的內部原理其實你去看源碼,你會發現和我們的 ReentrantLock 的實現是極其類似的,包括公平與非公平策略的支持,只不過,AQS 里面的 state 在前者的實現中,一般小於等於一(除非重入鎖),而后者的 state 則小於等於十,記錄的是剩余可用臨界資源數量。

所以,semaphore 天生就存在一個問題,如果某個線程重入了臨界區,可用臨界資源的數量是否需要減少?

停車場一共十個停車位,一輛車進去並占有了一個停車位,過了一段時間,這個向管理員報告,我還要占用一個停車位,先不管他占兩個干啥,此時的管理員會同意嗎?

實際上,在 Java 這個管理員看來,已經進入臨界區的線程是「老爺」,提出的要求都會優先滿足,即便他自身占有的資源並沒有釋放。

所以,在 Semaphore 機制里,一個線程進入臨界區之后占用掉所有的臨界資源都是可能的。

倒計時門栓 CountDownLatch

下面我們來看看這個 CountDownLatch,名字聽起來挺高級,究竟提供了怎樣的功能呢?

有這么一個常見的場景,我們一起來看看:

大家日常經常使用的拼多多,一件商品至少需要兩到三人拼團,商家才會發貨。

這里,我們不去研究它的商業模式,不管他是怎么實現盈利的,就這么一種場景,如果要用基本的並發 API 來實現,你可能會想到:

來一個線程阻塞一次,知道達到指定的數量后,全部喚醒

對,沒錯,CountDownLatch 內部就是這樣實現的,輪子已經幫你造好了,我們來看看該怎么實現上述的模型案例:

image

多運行幾次,你會發現結果不會錯,拼團的人先后順序可能不同,但商家一定是在三個人都准備好了之后才會發貨。

除此之外,它還有更多的應用,比如百米賽跑,只有當所有運動員都准備好了之后,裁判員才會吹響哨子,等等等等。

實現原理也基本和顯式鎖類似,不同點依然在於對 state 的控制,CountDownLatch 只判斷 state 是否等於零,不等於零就說明時機未到,阻塞當前線程。

而每一次的 countDown 方法調用都會減少一次倒計時資源,直至為零才喚醒阻塞的線程。

循環屏障 CyclicBarrier

CyclicBarrier 其實和 CountDownLatch 很像,我們先介紹完 CyclicBarrier,然后再和你一起去比較比較他倆的區別和相似點。

考慮這么一個場景:

公寓的班車總是在公寓樓下裝滿一車人之后,出發並開到地鐵站,接着再回來接下一班人。

這么一個場景,我們考慮該怎么實現:

image

效果大概就是這個樣子:

image

CyclicBarrier 就像一個屏障,實例化的時候需要傳入兩個參數,第一個參數指定我們的屏障最多攔截多少個線程后就打開屏障,第二個參數指明最后一個到達屏障的線程需要額外做的操作。

一般而言,最后一個線程到達屏障后,屏障將會打開,釋放前面所有的線程,並在最后重新關上屏障。

CyclicBarrier 只需要用到一個 await 就可以完成所有的功能,我們總結下該方法的實現邏輯:

  1. 首先,減少一次可用資源數量
  2. 如果可用資源數為零,則說明自己是最后一個線程,於是會執行我們傳入的額外操作,喚醒所有已經到達在等待的線程,並重新開啟一個屏障計數。
  3. 否則說明自己不是最后一個線程,於是將自身線程在一個循環當中阻塞到一個條件隊列上

好了,看完 CyclicBarrier 你會發現,它真的很類似我們的倒計時門栓,下面我們就來闡述他倆的區別與聯系。

第一個區別

倒計時門栓 CountDownLatch 一旦被打開后就不能再次合上,也是說只要被調用了足夠次數的 countDown,await 方法就會失效,它是一次性的。

CyclicBarrier 是循環發生的,當最后一個線程到達屏障,會優先重置屏障計數,屏障再次開啟攔截阻隔。

第二個區別

CountDownLatch 是計數器, 線程來一個就記一個,此期間不阻塞線程,當達到指定數量之后才會去喚醒外部等待的線程,也就是說外部是有一個乃至多個線程等待一個條件滿足之后才能繼續執行,而這個條件就是滿足一定數量的線程,這樣才能激活當前外部線程的繼續執行。

CyclicBarrier 像一個柵欄,來一個線程阻塞一個,直到阻塞了指定數量的線程后,一次性全部激活,讓他們同時執行,像一個百米沖刺一樣。

最后的最后

好了,以上就是我們 Java 並發包下面比較好用的三個工具類,其中前兩個的底層實現幾乎完全依賴顯式鎖的原理方法,后一個則是使用的顯式鎖加條件變量重新造的輪子,都是非常好用的工具!

除此之外還要說一點的是,整個並發這塊內容,基本核心的東西我們都已經介紹完了,共計十四篇文章,從基本的線程概念,到鎖原理,到線程池,再到異步任務,自認為總結的足夠細致了,不知道你了解了多少呢?

記不住沒關系,我也為你提供了一份思維導圖的總結,羅列了上述基本的內容,你可以對照着進行回顧,同時也歡迎你私信我討論探究。

獲取方式:公眾號回復「並發」或是直接去我的 Github 下載。

ps:我也要放假啦,祝福大家新春快樂,春節期間就不更文了,節后我們將開啟新的篇章,系統的總結『數據庫』相關的技術及原理,盡請關注!

關注公眾不迷路,一個愛分享的程序員。

公眾號回復「1024」加作者微信一起探討學習!

每篇文章用到的所有案例代碼素材都會上傳我個人 github

https://github.com/SingleYam/overview_java

歡迎來踩!

YangAM 公眾號


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM