併發 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 新增乙個許可,從而可能釋放乙個正在阻塞的獲取者。但是,不...