
[TOC]
## 一、什么是信號量
“信號量”在編程術語中使用單詞semaphore,那什么是“信號量”?信號量就好比你家廚房入口架子上擺了三把鍋。

* 如果你的孩子熱奶拿走一把,你的老婆熱湯拿走一把,你的媽媽做菜拿走一把,你想煮面條就沒有鍋了。當你看到這種情況,你就不會進入廚房了,你處于等待狀態。也就說廚房按照“鍋的數量”作為信號量,只能容納三個人(線程)。
* 當你的老婆熱完湯之后,把鍋重新放回架子上,你就可以去獲得一個鍋,你就可以進入廚房了。
## 二、信號量類Semaphore
通過上文的介紹,我們可以總結出信號量的重要組成部分
* 計數器:計算信號量的使用情況,鍋(信號)被使用一次減1,鍋(信號)被還回一次加1
* 等待隊列:當任務數量大于信號量數量上限的時候,任務進入等待隊列
信號量在JDK中是由 java.util.concurrent.Semaphore 實現的,Semaphore提供了兩個構造函數。permits參數代表信號量的數量(鍋的數量),fair代表信號量的獲取是否遵循公平原則。所謂的公平原則就是:先啟動的線程先調用`semaphore.acquire();`方法,就先得到一個信號“鍋”(permit),遵循先來后到的原則。
~~~java
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
~~~
**常用方法列表**
|方法名|作用|
|---|---|
|acquire() |獲取一個permit,在獲取到permit之前,線程處于阻塞狀態|
|tryAcquire()|嘗試獲取一個permit,獲取成功返回true,否則返回false,不阻塞線程|
|tryAcquire(long timeout, TimeUnit unit)|和tryAcquire()大部分實現一樣,區別是提供超時設置,在超時時間范圍內多次嘗試,不阻塞線程。|
|availablePermits()|獲取目前剩余的信號permit的數量|
|release()|釋放一個permit,并喚醒一個等待信號permit的線程|
|hasQueuedThreads()|返回值boolean類型,判斷等待隊列中是否存在等待線程|
|getQueueLength()|獲取等待隊列中等待線程的數量|
## 三、實現限流器
通過上面的介紹,我相信大家肯定可以想到Semaphore的應用場景。比如:
* 醫院門診排號器,三個在崗醫生就是3個信號permit,當超出信號量數量的時候,想就診就只能等待
* 停車場停車功能,n個車位就是n個信號permit,當超出信號量數量的時候,想停車也只能等待
應用場景還有很多很多,大家自己去發會創造力吧。其實無論多少種應用場景說白了:**Semaphore實現的就是一個限流器**。我們還是以我們家的廚房kitchen里面的三把鍋wok為例,實現基于信號量的限流。
~~~java
public class TestKitchenSemaphore {
//信號量-3把鍋
private static Semaphore threeWoks = new Semaphore(3);
public static void main(String[] args) throws InterruptedException {
//模擬5個人搶占3把鍋的場景
for(int i=0;i < 5;i++){
Thread.sleep(1000); //模擬進入廚房的先后順序,存在時間間隔
new Thread(() -> {
try {
threeWoks.acquire(); //獲取一個permit,信號量計數器減1
System.out.println(Thread.currentThread().getName()
+ "拿走了一把鍋,還剩" + threeWoks.availablePermits() + "把鍋");
Thread.sleep(new Random().nextInt(5000)); //模擬使用鍋的時長
threeWoks.release();//釋放permit,信號量計數器加1
System.out.println(Thread.currentThread().getName()
+ "還回一把鍋,還剩" + threeWoks.availablePermits() + "把鍋");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
~~~
上文代碼的輸出如下,我們可以看到每acquire一次信號量減1,每release一次信號量加1。信號量的上限是3,下限是0。當達到上限的時候,只有等先占據鍋permit的線程釋放,其他線程才能獲取到鍋permit。
~~~
Thread-0拿走了一把鍋,還剩2把鍋
Thread-1拿走了一把鍋,還剩1把鍋
Thread-2拿走了一把鍋,還剩0把鍋 => 備注:5個線程只能獲取3個鍋(上限)
Thread-1還回一把鍋,還剩1把鍋
Thread-3拿走了一把鍋,還剩0把鍋 => 備注:被還回才能被再次占用,不超過3
Thread-0還回一把鍋,還剩1把鍋
Thread-4拿走了一把鍋,還剩0把鍋 => 備注:被還回才能被再次占用,不超過3
Thread-2還回一把鍋,還剩1把鍋
Thread-3還回一把鍋,還剩2把鍋
Thread-4還回一把鍋,還剩3把鍋 => 備注:用完依次釋放
~~~
- 線程
- 1.進程和線程-鎖與信號量
- 2.Thread類線程狀態轉換
- 2.并發與并行-同步與異步
- 4.線程池
- 5.對象級別與類級別的同步鎖
- 6.創建線程的四種方式
- 7.臨界區-阻塞-活鎖-死鎖
- 2.JMM多線程模型
- JUC
- BlockingQueue
- ArrayBlockingQueue
- DelayQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
- SynchronousQueue
- BlockingDeque
- ConcurrentHashMap
- CountDownLatch
- CyclicBarrier
- Exchanger
- AtomicInteger
- Lock
- Condition
- ReentrantLock讀寫鎖
- StampedLock
- Semaphore