當我們創建一個可擴展大小的線程池,並且需要在線程池內同時讓有限數目的線程並發運行時,就需要用到Semaphore(信號燈機制),Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的線程數目,它是一個計數信號量,從概念上講,信號量維護瞭一個許可集合,如有必要,在許可可用前會阻塞每一個acquire(),然後再獲取該許可,每個release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。
在線程池內創建線程並運行時,每個線程必須從信號量獲取許可,從而保證可以使用該項。該線程結束後,線程返回到池中並將許可返回到該信號量,從而允許其他線程獲取該項。註意,調用acquire() 時無法保持同步鎖定,因為這會阻止線程返回到池中。信號量封裝所需的同步,以限制對池的訪問,這同維持該池本身一致性所需的同步是分開的。下面通過一個例子加以說明:
[java]
public class SemaphoreTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final Semaphore sp = new Semaphore(3);
for(int i=0;i<5;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
sp.acquire();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("線程" + Thread.currentThread().getName() +
"進入,當前已有" + (3-sp.availablePermits()) + "個並發");
try {
Thread.sleep((long)(Math.random()*10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程" + Thread.currentThread().getName() +
"即將離開");
sp.release();
//下面代碼有時候執行不準確,因為其沒有和上面的代碼合成原子單元
System.out.println("線程" + Thread.currentThread().getName() +
"已離開,當前已有" + (3-sp.availablePermits()) + "個並發");
}
};
service.execute(runnable);
}
}
}
該例子定義瞭一個newCachedThreadPool,在該Pool中利用for循環同時創建5個線程,現在通過Semaphore,創建一個隻允許在線程池中有3個線程並發運行,sp.acquire()表示某個線程獲得瞭一個信號燈,開始運行,在運行結束時,通過sp.release()還回這個信號燈,以便剩下的線程獲得信號燈運行,sp.availablePermits()指的是當前信號燈庫中有多少個可以被使用,由於例子中定義有3個信號燈,所以3-sp.availablePermits()就代表瞭當前有多少個線程在並發運行,上例運行結果如下:
[java]
線程pool-1-thread-1進入,當前已有2個並發
線程pool-1-thread-2進入,當前已有2個並發
線程pool-1-thread-3進入,當前已有3個並發
線程pool-1-thread-1即將離開
線程pool-1-thread-1已離開,當前已有2個並發
線程pool-1-thread-4進入,當前已有3個並發
線程pool-1-thread-3即將離開
線程pool-1-thread-3已離開,當前已有2個並發
線程pool-1-thread-5進入,當前已有3個並發
線程pool-1-thread-2即將離開
線程pool-1-thread-2已離開,當前已有2個並發
線程pool-1-thread-4即將離開
線程pool-1-thread-4已離開,當前已有1個並發
線程pool-1-thread-5即將離開
線程pool-1-thread-5已離開,當前已有0個並發
Semaphore作為互斥鎖使用:
當信號量初始化為 1,使得它在使用時最多隻有一個可用的許可,從而可用作一個相互排斥的鎖。這通常也稱為二進制信號量,因為它隻能有兩種狀態:一個可用的許可,或零個可用的許可。按此方式使用時,與傳統互斥鎖最大不同就是在釋放的時候並不是必須要擁有鎖的對象釋放,也可以由其他的對象釋放,因為信號量沒有所有權的概念。在某些專門的上下文(如死鎖恢復)中這會很有用。
Semaphore的構造方法有兩種:
第一種:
[java]
Semaphore(int permits) //用給定的許可數和非公平的公平設置創建一個 Semaphore。
第一種構造方法創建的信號燈,現在在獲取的時候是隨機的,沒有一定的順序,例如上例中,在前三個線程中的一個運行完畢以後,釋放一個信號燈,剩下的兩個線程就會隨機的一個線程得到這個信號燈而運行。
第二種:
[java]
Semaphore(int permits, boolean fair) //用給定的許可數和給定的公平設置創建一個 Semaphore
第二種構造方法可選地接受一個公平 參數。當設置為 false 時,此類不對線程獲取許可的順序做任何保證。特別地,闖入 是允許的,也就是說可以在已經等待的線程前為調用acquire() 的線程分配一個許可,從邏輯上說,就是新線程將自己置於等待線程隊列的頭部。當公平設置為 true 時,信號量保證對於任何調用acquire() 方法的線程而言,都按照處理它們調用這些方法的順序(即先進先出;FIFO)來選擇線程、獲得許可。註意,FIFO 排序必然應用到這些方法內的指定內部執行點。所以,可能某個線程先於另一個線程調用瞭acquire(),但是卻在該線程之後到達排序點,並且從方法返回時也類似。還要註意,非同步的tryAcquire() 方法不使用公平設置,而是使用任意可用的許可。
通常,應該將用於控制資源訪問的信號量初始化為公平的,以確保所有線程都可訪問資源。為其他的種類的同步控制使用信號量時,非公平排序的吞吐量優勢通常要比公平考慮更為重要。