java.util.concurrent.Semaphore 類是一個計數信號量。這意味著它具有兩個主要方法:
acquire()
release()
計數信號量由一個指定數量的 "許可" 初始化。每調用一次 acquire (),一個許可會被調用線程取走。每調用一次 release (),一個許可會被返還給信號量。因此,在沒有任何 release () 調用時,最多有 N 個線程能夠通過 acquire () 方法,N 是該信號量初始化時的許可的指定數量。這些許可只是一個簡單的計數器。這裡沒啥奇特的地方。
Semaphore 用法#
信號量主要有兩種用途:
- 保護一個重要 (代碼) 部分防止一次超過 N 個線程進入。
- 在兩個線程之間發送信號。
保護重要部分#
如果你將信號量用於保護一個重要部分,試圖進入這一部分的代碼通常會首先嘗試獲得一個許可,然後才能進入重要部分 (代碼塊),執行完之後,再把許可釋放掉。比如這樣:
Semaphore semaphore = new Semaphore(1);
//critical section
semaphore.acquire();
...
semaphore.release();
在線程之間發送信號#
如果你將一個信號量用於在兩個線程之間傳送信號,通常你應該用一個線程調用 acquire () 方法,而另一個線程調用 release () 方法。
如果沒有可用的許可,acquire () 調用將會阻塞,直到一個許可被另一個線程釋放出來。同理,如果無法往信號量釋放更多許可時,一個 release () 調用也會阻塞。
通過這個可以對多個線程進行協調。比如,如果線程 1 將一個對象插入到了一個共享列表 (list) 之後之後調用了 acquire (),而線程 2 則在從該列表中獲取一個對象之前調用了 release (),這時你其實已經創建了一個阻塞隊列。信號量中可用的許可的數量也就等同於該阻塞隊列能夠持有的元素個數。
公平#
沒有辦法保證線程能夠公平地可從信號量中獲得許可。也就是說,無法擔保掉第一個調用 acquire () 的線程會是第一個獲得一個許可的線程。如果第一個線程在等待一個許可時發生阻塞,而第二個線程前來索要一個許可的時候剛好有一個許可被釋放出來,那麼它就可能會在第一個線程之前獲得許可。
如果你想要強制公平,Semaphore 類有一個具有一個布爾類型的參數的構造子,通過這個參數以告知 Semaphore 是否要強制公平。強制公平會影響到並發性能,所以除非你確實需要它否則不要啟用它。
以下是如何在公平模式創建一個 Semaphore 的示例:
Semaphore semaphore = new Semaphore(1, true);
更多方法#
java.util.concurrent.Semaphore 類還有很多方法,比如:
- availablePermits()
- acquireUninterruptibly()
- drainPermits()
- hasQueuedThreads()
- getQueuedThreads()
- tryAcquire()
等等