信號量Semaphore是一個併發工具類,用來控制可同時併發的線程數,其內部維護了一組虛擬許可,通過構造器指定許可的數量,每次線程執行操作時先通過acquire方法獲得許可,執行完畢再通過release方法釋放許可。如果無可用許可,那麼acquire方法將一直阻塞,直到其它線程釋放許可。
線程池用來控制實際工作的線程數量,通過線程複用的方式來減小內存開銷。線程池可同時工作的線程數量是一定的,超過該數量的線程需進入線程隊列等待,直到有可用的工作線程來執行任務。
使用Seamphore,你創建了多少線程,實際就會有多少線程進行執行,只是可同時執行的線程數量會受到限制。但使用線程池,你創建的線程只是作爲任務提交給線程池執行,實際工作的線程由線程池創建,並且實際工作的線程數量由線程池自己管理。
簡單來說,線程池實際工作的線程是work線程,不是你自己創建的,是由線程池創建的,並由線程池自動控制實際併發的work線程數量。而Seamphore相當於一個信號燈,作用是對線程做限流,Seamphore可以對你自己創建的的線程做限流(也可以對線程池的work線程做限流),Seamphore的限流必須通過手動acquire和release來實現。
區別就是兩點:
- 使用線程池,實際工作線程由線程池創建;使用Seamphore,實際工作的線程由你自己創建
- 限流是否自動實現:線程池自動,Seamphore手動
Semaphore
public static void testSeamphore() {
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 5; i++) {
Thread thread = new Thread() {
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " start running **********************");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " stop running ----------------------");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.setName("my thread " + i);
thread.start();
}
}
//// 結果,創建了5個線程
my thread 0 start running **********************
my thread 1 start running **********************
my thread 0 stop running ----------------------
my thread 1 stop running ----------------------
my thread 2 start running **********************
my thread 3 start running **********************
my thread 3 stop running ----------------------
my thread 2 stop running ----------------------
my thread 4 start running **********************
my thread 4 stop running ----------------------
線程池
public static void testPool() {
ExecutorService executorService = new ThreadPoolExecutor(2, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
for (int i = 0; i < 5; i++) {
Thread thread = new Thread() {
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " start running **********************");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " stop running ----------------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.setName("my thread " + i);
executorService.submit(thread);
}
executorService.shutdown();
}
//// 結果,只會創建兩個線程
pool-1-thread-1 start running **********************
pool-1-thread-2 start running **********************
pool-1-thread-2 stop running ----------------------
pool-1-thread-1 stop running ----------------------
pool-1-thread-1 start running **********************
pool-1-thread-2 start running **********************
pool-1-thread-1 stop running ----------------------
pool-1-thread-2 stop running ----------------------
pool-1-thread-1 start running **********************
pool-1-thread-1 stop running ----------------------
Semaphore作爲互斥鎖的體現
Semaphore實現互斥鎖的方式是使用初始值爲1的Semaphore對象,這樣每條線程獲取許可後必須釋放許可,其它線程才能獲取許可,當前擁有許可的線程就擁有了互斥鎖。
public static void testMutex() {
Semaphore semaphore = new Semaphore(1);
for (int i = 0; i < 5; i++) {
new Thread() {
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "已獲得許可");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "已釋放許可");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
//// 結果
Thread-0已獲得許可
Thread-0已釋放許可
Thread-1已獲得許可
Thread-1已釋放許可
Thread-2已獲得許可
Thread-2已釋放許可
Thread-3已獲得許可
Thread-3已釋放許可
Thread-4已獲得許可
Thread-4已釋放許可
Semaphore先release後acquire
Seamphore有一種特殊的使用場景,即先釋放許可,後申請許可,此時會額外增加一個許可。
實際編程中要額外小心,如下的實例,通過new Semaphore(0)創建的信號量,默認許可數是0,如果先調用release,會增加一個許可,再次acquire便可以獲取新增的許可。
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
System.out.println(semaphore.availablePermits());
semaphore.release();
System.out.println(semaphore.availablePermits());
semaphore.acquire(); //阻塞
System.out.println(semaphore.availablePermits());
}
//// 結果
0
1
0
所以上面的代碼實際上不會發生阻塞,而是直接輸出0 1 0。本例中如果將release和acquire調換位置,則一定會發生阻塞。