併發程式設計之 CyclicBarrier 原始碼分析

2022-07-05 18:18:09 字數 3359 閱讀 2088

在之前的介紹 countdownlatch 的文章中,countdown 可以實現多個執行緒協調,在所有指定執行緒完成後,主線程才執行任務。

但是,countdownlatch 有個缺陷,這點 jdk 的文件中也說了:他只能使用一次。在有些場合,似乎有些浪費,需要不停的建立 countdownlatch 例項,jdk 在 countdownlatch 的文件中向我們介紹了 cyclicbarrier——迴圈柵欄。具體使用參見文章 併發程式設計之 執行緒協作工具類。

該類結構如下:

有乙個我們常用的方法 await,還有乙個內部類,generation ,僅有乙個引數,有什麼作用呢?

在 cyclicbarrier 中,有乙個 「代」 的概念,因為 cyclicbarrier 是可以復用的,那麼每次所有的執行緒通過了柵欄,就表示一代過去了,就像我們的新年一樣。當所有人跨過了元旦,日曆就更新了。

為什麼需要這個呢?後面我們看原始碼的時候在細說,現在說有點不太容易懂。

再看看構造方法,有 2 個構造方法:

public cyclicbarrier(int parties) 

public cyclicbarrier(int parties, runnable barrieraction)

如果使用 cyclicbarrier 就知道了,cyclicbarrier 支援在所有執行緒通過柵欄的時候,執行乙個執行緒的任務。

parties 屬性就是執行緒的數量,這個數量用來控制什麼時候釋放開啟柵欄,讓所有線

程通過。

好了,cyclicbarrier 的最重要的方法就是 await 方法,當執行了這樣乙個方法,就像是樹立了乙個柵欄,將執行緒擋住了,只有所有的執行緒都到了這個柵欄上,柵欄才會開啟。

看看這個方法的實現。

**加注釋如下:

private int dowait(boolean timed, long nanos)

throws interruptedexception, brokenbarrierexception,

timeoutexception

// 獲取下標

int index = --count;

// 如果是 0 ,說明到頭了

if (index == 0) finally

}for (;;) catch (interruptedexception ie) else

}// 當有任何乙個執行緒中斷了,會呼叫 breakbarrier 方法.

// 就會喚醒其他的執行緒,其他執行緒醒來後,也要丟擲異常

if (g.broken)

throw new brokenbarrierexception();

// g != generation >>> 正常換代了

// 一切正常,返回當前執行緒所在柵欄的下標

// 如果 g == generation,說明還沒有換代,那為什麼會醒了?

// 因為乙個執行緒可以使用多個柵欄,當別的柵欄喚醒了這個執行緒,就會走到這裡,所以需要判斷是否是當前代。

// 正是因為這個原因,才需要 generation 來保證正確。

if (g != generation)

return index;

// 如果有時間限制,且時間小於等於0,銷毀柵欄,並丟擲異常

if (timed && nanos <= 0l)

}} finally

}

**雖然長,但整體邏輯還是很簡單的。總結一下該方法吧。

首先,每個 cyclicbarrier 都有乙個 lock,想執行 await 方法,就必須獲得這把鎖。所以,cyclicbarrier 在併發情況下的效能是不高的。

一些執行緒中斷的判斷,注意,cyclicbarrier 中,只有有乙個執行緒中斷了,其餘的執行緒也會丟擲中斷異常。並且,這個 cyclicbarrier 就不能再次使用了。

每次執行緒呼叫一次 await 方法,表示這個執行緒到了柵欄這裡了,那麼就將計數器減一。如果計數器到 0 了,表示這是這一代最後乙個執行緒到達柵欄,就嘗試執行我們構造方法中輸入的任務。最後,將代更新,計數器重置,並喚醒所有之前等待在柵欄上的執行緒。

如果不是最後乙個執行緒到達柵欄了,就使用 condition 的 await 方法阻塞執行緒。如果等待過程中,執行緒中斷了,就丟擲異常。這裡,注意一下,如果中斷的執行緒的使用 cyclicbarrier 不是這代的,比如,在最後一次執行緒執行 signalall 後,並且更新了這個「代」物件。在這個區間,這個執行緒被中斷了,那麼,jdk 認為任務已經完成了,就不必在乎中斷了,只需要打個標記。所以,catch 裡的 else 判斷用於極少情況下出現的判斷——任務完成,「代」 更新了,突然出現了中斷。這個時候,cyclicbarrier 是不在乎的。因為任務已經完成了。

當有乙個執行緒中斷了,也會喚醒其他執行緒,那麼就需要判斷 broken 狀態。

如果這個執行緒被其他的 cyclicbarrier 喚醒了,那麼 g 肯定等於 generation,這個事件就不能 return 了,而是繼續迴圈阻塞。反之,如果是當前 cyclicbarrier 喚醒的,就返回執行緒在 cyclicbarrier 的下標。完成了一次衝過柵欄的過程。

從 await 方法看,cyclicbarrier 還是比較簡單的,jdk 的思路就是:設定乙個計數器,執行緒每呼叫一次計數器,就減一,並使用 condition 阻塞執行緒。當計數器是0的時候,就喚醒所有執行緒,並嘗試執行建構函式中的任務。由於 cyclicbarrier 是可重複執行的,所以,就需要重置計數器。

cyclicbarrier 還有乙個重要的點,就是 generation 的概念,由於每乙個執行緒可以使用多個 cyclicbarrier,每個 cyclicbarrier 又都可以喚醒執行緒,那麼就需要用代來控制,如果代不匹配,就需要重新休眠。同時,這個代還記錄了執行緒的中斷狀態,如果任何執行緒中斷了,那麼所有的執行緒都會丟擲中斷異常,並且 cyclicbarrier 不再可用了。

總而言之,cyclicbarrier 是依靠乙個計數器實現的,內部有乙個 count 變數,每次呼叫都會減一。當一次完整的柵欄活動結束後,計數器重置,這樣,就可以重複利用了。

而他和 countdownlatch 的區別在於,countdownlatch 只能使用一次就 over 了,cyclicbarrier 能使用多次,可以說功能類似,cyclicbarrier 更強大一點。並且 cyclicbarrier 攜帶了乙個在柵欄處可以執行的任務。更加靈活。

下面來一張圖,說說 cyclicbarrier 的流程。和 countdownlatch 類似:

Java併發程式設計系列 CyclicBarrier

cyclicbarrier簡介 cyclicbarrier也是基於reentrantlock和condition的乙個同步工具類,它的作用是讓一些執行緒到達某個公共屏障點時,等待未到達的執行緒。當所有執行緒到達屏障點時,繼續往下執行。先看乙個例子 public class cyclicbarrier...

併發tools之柵欄CyclicBarrier

前言 cyclicbarrier翻譯過來就是 迴圈的屏障,這個類是乙個可以重複利用的屏障類.它允許一組執行緒相互等待,直到全部到達某個公共屏障點,然後所有的這組執行緒再同步往後執行 await 函式每被呼叫依次,計數便會減少1,並阻塞當前執行緒 當計數減至0,阻塞解除 countdownlatch和...

併發程式設計之併發佇列

jdk 中提供了一系列場景的併發安全佇列。總的來說,按照實現方式的不同可分為阻塞佇列和非阻塞佇列,前者使用鎖實現,而後者則使用cas 非阻塞演算法實現。1 非阻塞佇列 concurrentlinkedqueue concurrentlinkedqueue是無界非阻塞佇列,內部使用單項鍊表實現 其中有...