699
技術社區[雲棲]
信號量
Semaphore(信號量) 是一個線程同步結構,用於在線程間傳遞信號,以避免出現信號丟失(譯者注:下文會具體介紹),或者像鎖一樣用於保護一個關鍵區域。自從5.0開始,jdk 在java.util.concurrent包裏提供了Semaphore 的官方實現,因此大家不需要自己去實現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塊中被執行。這樣可以保在關鍵區域的代碼拋出異常的情況下,信號也一定會被釋放。
文章轉自 並發編程網-ifeve.com
最後更新:2017-05-22 18:01:48