三個好用的併發工具類

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

它們分別是:git

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

因此,既然是工具類,那麼必然是離不開特定的場景的,因而相互之間沒有誰優誰劣,只有誰更合適。程序員

信號量 Semaphore

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

假如如今有一個停車場,裏面有隻十個停車位,當着十個停車位都被佔用了,外面的車就不容許進入了,就必須在外面等着。出來一輛車才容許進去一輛車數據庫

這個場景不一樣於咱們通常的併發場景,通常來講,咱們的臨界資源只能容許一個線程進行訪問,其餘線程都地等着。微信

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

這種情境使用原始的那一套也是能實現的,但那叫「造輪子」,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 公衆號

相關文章
相關標籤/搜索