Java線程3-JUC

Java線程3-JUC

JUC的實現基礎

JMM

AQS

CAS

volatile

Java併發編程:volatile關鍵字解析
Java volatile 關鍵字底層實現原理解析

JUC相關工具類

CountDownLatch

CountDownLatch 可以理解爲是一個計數器,通過對計數值 count 的判定來控制線程的執行,只有計數值 count 爲 0 時,纔會觸發對應線程的執行;
CountDownLatch 的構造函數可以設置 count 值,使用 countDown() 會讓 count 減 1。
當 count > 0 時,await() 會造成阻塞,直到 count 變爲 0,await() 結束阻塞。
當 count = 1 時,它的作用類似於 wait() 和 notify() 的作用。
如果我想讓其他線程執行指定程序,其他線程執行完畢後我再執行,這時就可以使用 CountDownLatch,但是 count 計數無法被重置;如果需要重置計數,請考慮使用 CyclicBarrier。

API

public CountDownLatch(int count)

構造器,爲 CountDownLatch 設定初始值,count 就是需要等待的線程數量

public void countDown()

使計數值 count 減 1

public void await() throws InterruptedException

掛起當前線程,等待計數值 count 變爲 0 後再執行

public boolean await(long timeout, TimeUnit unit) throws InterruptedException

掛起當前線程,等待計數值變爲 0 後再執行,等待時間通過 timeout * unit 進行設定;
若經過指定時間段後,計數值還是沒有變爲 0,則恢復當前線程執行

代碼實例

實例1. 主線程等待2個子線程執行完畢後再執行

demo_1

參考文檔

《Java面經-百度准入職老哥整理》7.1.1 CountDownLatch
Java併發編程:CountDownLatch、CyclicBarrier和Semaphore
CountDownLatch 使用詳解
大白話說java併發工具類-CountDownLatch,CyclicBarrier
高併發編程-CountDownLatch深入解析
CountDownLatch的介紹和使用
圖解幾個好玩的併發輔助工具類

CyclicBarrier

該類從字面意思理解爲循環屏障, 它可以協同多個線程, 讓多個線程在這個屏障前等待, 直到所有線程都到達了這個屏障時, 再一起執行後面的操作,這裏的屏障可以理解爲 await() 方法所在的位置
假如每個線程各有一個 await, 任何一個線程運行到 await 方法時就阻塞, 直到最後一個線程運行到 await 時才同時執行線程後續操作並返回。
和 CountDownLatch 相比, 它只有 await 方法, 而 CountDownLatch 是使用 countDown() 方法將計數器減到 0, CountDownLatch 創建的參數就是 countDown 的數量; CyclicBarrier 創建時的 int 參數是 await 的數量。

CyclicBarrier 也是一種多線程併發控制的實用工具,和 CountDownLatch 一樣具有等待計數的功能,但是相比於 CountDownLatch 功能更加強大。爲了理解 CyclicBarrier,這裏舉一個通俗的例子。開運動會時,會有跑步這一項運動,我們來模擬下運動員入場時的情況,假設有 6 條跑道,在比賽開始時,就需要 6 個運動員在比賽開始的時候都站在起點了,裁判員吹哨後才能開始跑步。跑道起點就相當於“barrier”,是臨界點,而這 6 個運動員就類比成線程的話,就是這 6 個線程都必須到達指定點了,意味着湊齊了一波,然後才能繼續執行,否則每個線程都得阻塞等待,直至湊齊一波即可。cyclic 是循環的意思,也就是說 CyclicBarrier 當多個線程湊齊了一波之後,仍然有效,可以繼續湊齊下一波。CyclicBarrier 的執行示意圖如下:
在這裏插入圖片描述
當多個線程都達到了指定點後,才能繼續往下繼續執行。這就有點像報數的感覺,假設 6 個線程就相當於 6 個運動員,到賽道起點時會報數進行統計,如果剛好是 6 的話,這一波就湊齊了,才能往下執行。CyclicBarrier 在使用一次後,下面依然有效,可以繼續當做計數器使用,這是與 CountDownLatch 的區別之一。這裏的 6 個線程,也就是計數器的初始值 6,是通過 CyclicBarrier 的構造方法傳入的。

CountDownLatch 與 CyclicBarrier 的比較

CountDownLatch 與 CyclicBarrier 都是用於控制併發的工具類,都可以理解成維護的就是一個計數器,但是這兩者還是各有不同側重點的:

  1. CountDownLatch 一般用於某個線程 A 等待若干個其他線程執行完任務之後,它才執行;而 CyclicBarrier 一般用於一組線程互相等待至某個狀態,然後這一組線程再同時執行;
    CountDownLatch 強調一個線程等多個線程完成某件事情。CyclicBarrier 是多個線程互等,等大家都完成,再攜手共進。
  2. 調用 CountDownLatch 的 countDown 方法後,當前線程並不會阻塞,會繼續往下執行;而調用 CyclicBarrier 的 await 方法,會阻塞當前線程,直到 CyclicBarrier 指定的線程全部都到達了指定點的時候,才能繼續往下執行;
  3. CountDownLatch 方法比較少,操作比較簡單,而 CyclicBarrier 提供的方法更多,比如能夠通過 getNumberWaiting(),isBroken()這些方法獲取當前多個線程的狀態,並且 CyclicBarrier 的構造方法可以傳入 barrierAction,指定當所有線程都到達時執行的業務功能;
  4. CountDownLatch 是不能複用的,而 CyclicBarrier 是可以複用的。

API

public CyclicBarrier(int parties)

CyclicBarrier 的 構造器,parties 指定有多少個線程須進行等待

public CyclicBarrier(int parties, Runnable barrierAction)

CyclicBarrier 的 構造器,parties 指定有多少個線程需進行等待,barrierAction 指定所有線程都執行到 await() 方法時再執行的操作

public int await() throws InterruptedException, BrokenBarrierException

當前線程掛起等待,即掛起當前線程,直到所有線程都達到屏障時再同時執行線程後續操作

public int await(long timeout, TimeUnit unit) throws InterruptedException,BrokenBarrierException,TimeoutException

掛起當前線程,直到所有線程都到達屏障時再同時執行線程後續操作,掛起時間通過 timeout * unit 指定;如果超過指定時間段後,還有線程沒有到達屏障就直接讓到達屏障的線程繼續執行後續任務。

public int getNumberWaiting()

獲取當前有多少線程正在進行等待

代碼實例

實例1. 線程等待實例

假若有若干個線程都要進行寫數據操作,並且只有所有線程都完成寫數據操作之後,這些線程才能繼續做後面的事情,此時就可以利用CyclicBarrier了:
demo_1
從輸出結果可以看出,每個寫入線程執行完寫數據操作之後,就在等待其他線程寫入操作完畢。
當所有線程寫入操作完畢之後,所有線程就繼續進行後續的操作了。

實例2. 通過 Runnable 添加額外操作

當所有線程都達到屏障後,可通過 Runnable 添加後序要做的操作,調度器會從上述線程中隨機選擇一個線程來完成這些操作。
如果說想在所有線程寫入操作完之後,進行額外的其他操作可以爲CyclicBarrier提供Runnable參數:
demo_2
從結果可以看出,當四個線程都到達barrier狀態後,會從四個線程中選擇一個線程去執行Runnable。

實例3. 通過 await(long timeout, TimeUnit unit) 指定線程等待時間

demo_3
上面的代碼在main方法的for循環中,故意讓最後一個線程啓動延遲,因爲在前面三個線程都達到barrier之後,等待了指定的時間發現第四個線程還沒有達到barrier,就拋出異常並繼續執行後面的任務。

實例4. CyclicBarrier 重用

demo_4
從執行結果可以看出,在初次的4個線程越過barrier狀態後,又可以用來進行新一輪的使用。而CountDownLatch無法進行重複使用。

實例5. CyclicBarrier 模擬所有運動員都入場後再開始比賽

demo_5

參考文檔

《Java面經-百度准入職老哥整理》7.1.2 CyclicBarrier
Java併發編程:CountDownLatch、CyclicBarrier和Semaphore
大白話說java併發工具類-CountDownLatch,CyclicBarrier

Semaphore

semphore:信號量,對於某個共享資源,限制可同時併發訪問的線程數量。

相關問題

new Semaphore(0) 表示什麼意思?

Semaphore(0) 是信號量的一個特殊用法,這種情況下的信號量只有先 release,然後才能 acquire
JAVA new Semaphore(0, true) 中的0該怎麼理解

API

public Semaphore(int permits) {

Semaphore 的構造器,permits 參數指定可同時訪問共享資源的線程數量,默認創建的爲非公平鎖

public Semaphore(int permits, boolean fair)

Semaphore 的構造器,permits 參數指定可同時訪問共享資源的線程數量,fair 參數指定是否使用公平分配的方式(即等待時間越久的越先分配)

public void acquire() throws InterruptedException

獲取一個信號量

public void acquire(int permits) throws InterruptedException

獲取多個信號量

public void release()

釋放一個信號量

public void release(int permits)

釋放多個信號量

代碼實例

實例1. 使用 Semaphore 模擬 8 個工人使用 5 臺及其的過程,其中每臺機器每次只能被一個工人使用

demo_1

參考文檔

詳解java中的同步工具類Semaphore
Java併發編程:CountDownLatch、CyclicBarrier和Semaphore
圖解幾個好玩的併發輔助工具類

Condition

Condition,條件變量,區別於 Object 的 wait()/notify() 的另外一種實現"等待/通知"模式的接口,主要用於實現"等待/通知"模式,但是性能上優於 Object 的 wait()/notify();
在這裏插入圖片描述
就像兩個線程交替執行,線程 A 通過 await() 進入阻塞狀態,等待線程 B 發出信號通知線程 A 恢復執行;而線程 B 則通過 signal() 方法來發出信號,通知線程 A 繼續執行;
Conditon 對象的創建依賴於 Lock 對象,在創建 Condition 對象之前,必須先創建 Lock 對象,然後才能通過 lock.newCondition() 方法來創建 Condition 對象;
另外,Condition 對象的 await()/signal() 方法必須包裹於 Lock 對象的 lock()/unlock() 方法之間;

Condition 原理解析

Condition 是 AQS 的內部類。
每個 Condition 對象都包含一個先進先出(FIFO)的等待隊列,隊列中的每個節點代表一個線程的引用,該線程就是在 Condition 對象上進行等待的線程。
如果一個線程調用了 Condition.await() 方法,那麼該線程將會釋放鎖並被構造成節點從尾部加入等待隊列進行等待。等待隊列示意圖如下:
在這裏插入圖片描述
等待隊列包含頭結點和尾節點。當一個線程調用 Condition.await() 方法,系統會以該線程的引用構造節點,並從尾部加入等待隊列。

API

void await() throws InterruptedException

功能等同於 Object 對象的 wait() 方法,阻塞當前線程,等待其他線程發出信號來通知當前線程,然後當前線程才能恢復執行

void signal()

功能等同於 Object 對象的 notify() 方法,發出信號,通知其他線程恢復執行

代碼實例

實例1. Condition 控制兩個線程間的同步執行

demo_1

應用

案例一:利用 Condition 實現生產者-消費者模式

參考文檔

Java併發之Condition
Java併發編程:線程間協作的兩種方式:wait、notify、notifyAll和Condition
Java多線程11:ReentrantLock的使用和Condition

Exchanger

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章