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

2021-09-11 12:32:54 字數 3425 閱讀 1368

併發 juc 包提供了很多任務具類,比如之前說的 countdownlatch,cyclicbarrier ,今天說說這個 semaphore——訊號量,關於他的使用請檢視往期文章併發程式設計之 執行緒協作工具類,今天的任務就是從原始碼層面分析一下他的原理。

如果先不看原始碼,根據以往我們看過的 countdownlatch cyclicbarrier 的原始碼經驗來看,semaphore 會怎麼設計呢?

首先,他要實現多個執行緒執行緒同時訪問乙個資源,類似於共享鎖,並且,要控制進入資源的執行緒的數量。

如果根據 jdk 現有的資源,我們是否可以使用 aqs 的 state 變數來控制呢?類似 countdownlatch 一樣,有幾個執行緒我們就為這個 state 變數設定為幾,當 state 達到了閾值,其他執行緒就不能獲取鎖了,就需要等待。當 semaphore 呼叫 release 方法的時候,就釋放鎖,將 state 減一,並喚醒 aqs 上的執行緒。

以上,就是我們的猜想,那我們看看 jdk 是不是和我們想的一樣。

首先看看 semaphore 的 uml 結構:

內部有 3 個類,繼承了 aqs。乙個公平鎖,乙個非公平鎖,這點和 reentrantlock 一摸一樣。

看看他的構造器:

public

semaphore

(int permits)

public

semaphore

(int permits, boolean fair)

複製**

兩個構造器,兩個引數,乙個是許可執行緒數量,乙個是是否公平鎖,預設非公平。

而 semaphore 有 2 個重要的方法,也是我們經常使用的 2 個方法:

semaphore.acquire();

// dosomeing.....

semaphore.release();

複製**

acquire 和 release 方法,我們今天重點看這兩個方法的原始碼,一窺 semaphore 的全貌。

**如下:

public

void

acquire

()throws interruptedexception

// 這是抽象類 aqs 的方法

public

final

void

acquiresharedinterruptibly

(int arg)

throws interruptedexception

複製**

// 這是抽象父類 sync 的方法,預設是非公平的

protected

inttryacquireshared

(int acquires)

複製**

// 非公平鎖的釋放鎖的方法

final

intnonfairtryacquireshared

(int acquires)

}複製**

這裡的釋放就是對 state 變數減一(或者更多)的。

返回了剩餘的 state 大小。

當返回值小於 0 的時候,說明獲取鎖失敗了,那麼就需要進入 aqs 的等待佇列了。**入下:

private

void

doacquiresharedinterruptibly

(int arg)

throws interruptedexception

}// 如果他的上乙個節點不是 head,就不能獲取鎖

// 對節點進行檢查和更新狀態,如果執行緒應該阻塞,返回 true。

if (shouldparkafte***iledacquire(p, node) &&

// 阻塞 park,並返回是否中斷,中斷則丟擲異常

parkandcheckinterrupt())

throw

new interruptedexception();

}} finally

}複製**

總的邏輯就是:

建立乙個分享型別的 node 節點包裝當前執行緒追加到 aqs 佇列的尾部。

如果這個節點的上乙個節點是 head ,就是嘗試獲取鎖,獲取鎖的方法就是子類重寫的方法。如果獲取成功了,就將剛剛的那個節點設定成 head。

如果沒搶到鎖,就阻塞等待。

該方法用於釋放鎖,**如下:

public

void

release

()public

final

boolean

releaseshared

(int arg)

return

false;

}// sync extends abstractqueuedsynchronizer

protected

final

boolean

tryreleaseshared

(int releases)

}複製**

這裡釋放鎖的邏輯寫在了抽象類 sync 中。邏輯簡單,就是對 state 變數做加法。

在加法成功後,執行doreleaseshared方法,這個方法是 aqs 的。

private

void

doreleaseshared

() // 成功設定成 0 之後,將 head 狀態設定成傳播狀態

else

if (ws == 0 &&

!compareandsetwaitstatus(h, 0, node.propagate))

continue; // loop on failed cas

}if (h == head) // loop if head changed

break;

}}複製**

該方法的主要作用就是從 aqs 的 head 節點開始喚醒執行緒,注意,這裡喚醒是 head 節點的下乙個節點,需要和doacquiresharedinterruptibly方法對應,因為doacquiresharedinterruptibly方法喚醒的當前節點的上乙個節點,也就是 head 節點。

至此,釋放 state 變數,喚醒 aqs 頭節點結束。

總結一下 semaphore 的原理吧。

總的來說,semaphore 就是乙個共享鎖,通過設定 state 變數來實現對這個變數的共享。當呼叫 acquire 方法的時候,state 變數就減去一,當呼叫 release 方法的時候,state 變數就加一。當 state 變數為 0 的時候,別的執行緒就不能進入**塊了,就會在 aqs 中阻塞等待。

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

在之前的介紹 countdownlatch 的文章中,countdown 可以實現多個執行緒協調,在所有指定執行緒完成後,主線程才執行任務。但是,countdownlatch 有個缺陷,這點 jdk 的文件中也說了 他只能使用一次。在有些場合,似乎有些浪費,需要不停的建立 countdownlatc...

併發程式設計之併發佇列

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

併發程式設計回顧 訊號量Semaphore

原先多執行緒併發程式設計的學習筆記和 整理一下貼上來。訊號量semaphore 根據jdk文件描述 乙個計數訊號量。從概念上講,訊號量維護了乙個許可集。如有必要,在許可可用前會阻塞每乙個 acquire 然後再獲取該許可。每個 release 新增乙個許可,從而可能釋放乙個正在阻塞的獲取者。但是,不...