Semaphore(信號量)是一個線程同步結構,用於在線程間傳遞信號,以避免出現信號丟失,或者像鎖一樣用於保護一個關鍵區域。自從 5.0 開始,jdk 在 java.util.concurrent 包里提供了 Semaphore 的官方實現,因此大家不需要自己去實現 Semaphore。
- 簡單的 Semaphore 實現
- 使用 Semaphore 來發出信號
- 可計數的 Semaphore
- 有上限的 Semaphore
- 把 Semaphore 當鎖來使用
簡單的 Semaphore 實現
下面是一個信號量的簡單實現:
public class Semaphore { private boolean signal = false; public synchronized void take() { this.signal = true; this.notify(); } public synchronized void release() throws InterruptedException{ while(!this.signal) wait(); this.signal = false; } }
Take 方法發出一個被存放在 Semaphore 內部的信號,而 Release 方法則等待一個信號,當其接收到信號后,標記位 signal 被清空,然后該方法終止。
使用這個 semaphore 可以避免錯失某些信號通知。用 take 方法來代替 notify,release 方法來代替 wait。如果某線程在調用 release 等待之前調用 take 方法,那么調用 release 方法的線程仍然知道 take 方法已經被某個線程調用過了,因為該 Semaphore 內部保存了 take 方法發出的信號。而 wait 和 notify 方法就沒有這樣的功能。
當用 semaphore 來產生信號時,take 和 release 這兩個方法名看起來有點奇怪。這兩個名字來源於后面把 semaphore 當做鎖的例子,后面會詳細介紹這個例子,在該例子中,take 和 release 這兩個名字會變得很合理。
使用 Semaphore 來產生信號
下面的例子中,兩個線程通過 Semaphore 發出的信號來通知對方
Semaphore semaphore = new Semaphore(); SendingThread sender = new SendingThread(semaphore); ReceivingThread receiver = new ReceivingThread(semaphore); receiver.start(); sender.start(); public class SendingThread { Semaphore semaphore = null; public SendingThread(Semaphore semaphore){ this.semaphore = semaphore; } public void run(){ while(true){ //do something, then signal this.semaphore.take(); } } } public class RecevingThread { Semaphore semaphore = null; public ReceivingThread(Semaphore semaphore){ this.semaphore = semaphore; } public void run(){ while(true){ this.semaphore.release(); //receive signal, then do something... } } }
可計數的 Semaphore
上面提到的 Semaphore 的簡單實現並沒有計算通過調用 take 方法所產生信號的數量。可以把它改造成具有計數功能的 Semaphore。下面是一個可計數的 Semaphore 的簡單實現。
public class CountingSemaphore { private int signals = 0; public synchronized void take() { this.signals++; this.notify(); } public synchronized void release() throws InterruptedException{ while(this.signals == 0) wait(); this.signals--; } }
有上限的 Semaphore
上面的 CountingSemaphore 並沒有限制信號的數量。下面的代碼將 CountingSemaphore 改造成一個信號數量有上限的 BoundedSemaphore。
public class BoundedSemaphore { private int signals = 0; private int bound = 0; public BoundedSemaphore(int upperBound){ this.bound = upperBound; } public synchronized void take() throws InterruptedException{ while(this.signals == bound) wait(); this.signals++; this.notify(); } public synchronized void release() throws InterruptedException{ while(this.signals == 0) wait(); this.signals--; this.notify(); } }
在 BoundedSemaphore 中,當已經產生的信號數量達到了上限,take 方法將阻塞新的信號產生請求,直到某個線程調用 release 方法后,被阻塞於 take 方法的線程才能傳遞自己的信號。
把 Semaphore 當鎖來使用
當信號量的數量上限是 1 時,Semaphore 可以被當做鎖來使用。通過 take 和 release 方法來保護關鍵區域。請看下面的例子:
BoundedSemaphore semaphore = new BoundedSemaphore(1); ... semaphore.take(); try{ //critical section } finally { semaphore.release(); }
在前面的例子中,Semaphore 被用來在多個線程之間傳遞信號,這種情況下,take 和 release 分別被不同的線程調用。但是在鎖這個例子中,take 和 release 方法將被同一線程調用,因為只允許一個線程來獲取信號(允許進入關鍵區域的信號),其它調用 take 方法獲取信號的線程將被阻塞,知道第一個調用 take 方法的線程調用 release 方法來釋放信號。對 release 方法的調用永遠不會被阻塞,這是因為任何一個線程都是先調用 take 方法,然后再調用 release。
通過有上限的 Semaphore 可以限制進入某代碼塊的線程數量。設想一下,在上面的例子中,如果 BoundedSemaphore 上限設為 5 將會發生什么?意味着允許 5 個線程同時訪問關鍵區域,但是你必須保證,這個 5 個線程不會互相沖突。否則你的應用程序將不能正常運行。
必須注意,release 方法應當在 finally 塊中被執行。這樣可以保在關鍵區域的代碼拋出異常的情況下,信號也一定會被釋放。