Java併發程式設計基礎三板斧之Semaphore
阿新 • • 發佈:2021-03-04
# 引言
最近可以進行個稅申報了,還沒有申報的同學可以趕緊去試試哦。不過我反正是從上午到下午一直都沒有成功的進行申報,一進行申報
就返回“當前訪問人數過多,請稍後再試”。為什麼有些人就能夠申報成功,有些人就直接返回失敗。這很明顯申報處理資源是有限的,
只能等別人處理完了在來處理你的,你如果運氣好可能重試幾次就輪到你了,如果運氣不好可能重試一天也可能輪不到你。
我反正已經是放棄了,等到夜深人靜的時候再來試試。作為一個程式設計師我們肯定知道這是個稅申請**app**的限流操作,如果還有不懂什麼
是限流操作的可以參考下這個文章[《高併發系統三大利器之限流》](https://mp.weixin.qq.com/s/AKWHHzoJgalTp3Fxvox2_A)。
比如個稅申報系統每臺機器只最多分別只能處理`1000`個請求,再多的請求就會把機器打掛。如果是多餘的請求就把這些請求拒絕掉。直接給你返回一句溫馨提示:“當前訪問人數過多,請稍後再試”,如果要實現這個功能大家想想可以通過哪些方法演算法來實現。
# 共享鎖、獨佔鎖
學習`semaphore`之前我們必須要先了解下什麼是共享鎖。在上一篇文章[《Java高併發程式設計基礎之AQS》](https://mp.weixin.qq.com/s/Scz_puodkdtoA6Zz7nh4uA)我們介紹了公平鎖於非公平鎖的區別。
- 共享鎖:它是允許多個執行緒同時獲取鎖,併發的訪問共享資源
- 獨佔鎖:也有人把它叫做“獨享鎖”,它是是獨佔的,排他的,只能被一個執行緒可持有,
當獨佔鎖已經被某個執行緒持有時,其他執行緒只能等待它被釋放後,才能去爭鎖,並且同一時刻只有一個執行緒能爭鎖成功。
# 什麼是Semaphore
在《**Java併發程式設計藝術**》(微信搜【**java金融**】回覆**電子書**可以免費獲取PDF版本)這一書中是這麼說的:
>Semaphore(訊號量)是用來控制同時訪問特定資源的執行緒數量,它通過協調各個執行緒,以保證合理的使用公共資源。很多年以來,我都覺得從字面上很難理解Semaphore所表達的含義,只能把它比作是控制流量的紅綠燈,比如XX馬路要限制流量,只允許同時有一百輛車在這條路上行使,其他的都必須在路口等待,所以前一百輛車會看到綠燈,可以開進這條馬路,後面的車會看到紅燈,不能駛入XX馬路,但是如果前一百輛中有五輛車已經離開了XX馬路,那麼後面就允許有5輛車駛入馬路,這個例子裡說的車就是執行緒,駛入馬路就表示執行緒在執行,離開馬路就表示執行緒執行完成,看見紅燈就表示執行緒被阻塞,不能執行。
- `Semaphore`機制是提供給執行緒搶佔式獲取許可,所以他可以實現公平或者非公平,類似於`ReentrantLock`。
說了這麼多我們來個實際的例子看一看,比如我們去停車場停車,停車場總共只有`5`個車位,但是現在有`8`輛汽車來停車,剩下的`3`輛汽車要麼等其他汽車開走後進行停車,或者去找別的停車位?
```java
/**
* @author: 公眾號【Java金融】
*/
public class SemaphoreTest {
public static void main(String[] args) throws InterruptedException {
// 初始化五個車位
Semaphore semaphore = new Semaphore(5);
// 等所有車子
final CountDownLatch latch = new CountDownLatch(8);
for (int i = 0; i < 8; i++) {
int finalI = i;
if (i == 5) {
Thread.sleep(1000);
new Thread(() -> {
stopCarNotWait(semaphore, finalI);
latch.countDown();
}).start();
continue;
}
new Thread(() -> {
stopCarWait(semaphore, finalI);
latch.countDown();
}).start();
}
latch.await();
log("總共還剩:" + semaphore.availablePermits() + "個車位");
}
private static void stopCarWait(Semaphore semaphore, int finalI) {
String format = String.format("車牌號%d", finalI);
try {
semaphore.acquire(1);
log(format + "找到車位了,去停車了");
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
} finally {
semaphore.release(1);
log(format + "開走了");
}
}
private static void stopCarNotWait(Semaphore semaphore, int finalI) {
String format = String.format("車牌號%d", finalI);
try {
if (semaphore.tryAcquire()) {
log(format + "找到車位了,去停車了");
Thread.sleep(10000);
log(format + "開走了");
semaphore.release();
} else {
log(format + "沒有停車位了,不在這裡等了去其他地方停車去了");
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void log(String content) {
// 格式化
DateTimeFormatter fmTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 當前時間
LocalDateTime now = LocalDateTime.now();
System.out.println(now.format(fmTime) + " "+content);
}
}
```
```java
2021-03-01 18:54:57 車牌號0找到車位了,去停車了
2021-03-01 18:54:57 車牌號3找到車位了,去停車了
2021-03-01 18:54:57 車牌號2找到車位了,去停車了
2021-03-01 18:54:57 車牌號1找到車位了,去停車了
2021-03-01 18:54:57 車牌號4找到車位了,去停車了
2021-03-01 18:54:58 車牌號5沒有停車位了,不在這裡等了去其他地方停車去了
2021-03-01 18:55:07 車牌號7找到車位了,去停車了
2021-03-01 18:55:07 車牌號6找到車位了,去停車了
2021-03-01 18:55:07 車牌號2開走了
2021-03-01 18:55:07 車牌號0開走了
2021-03-01 18:55:07 車牌號3開走了
2021-03-01 18:55:07 車牌號4開走了
2021-03-01 18:55:07 車牌號1開走了
2021-03-01 18:55:17 車牌號7開走了
2021-03-01 18:55:17 車牌號6開走了
2021-03-01 18:55:17 總共還剩:5個車位
```
從輸出結果我們可以看到`車牌號5`這輛車看見沒有車位了,就不在這個地方傻傻的等了,而是去其他地方了,但是`車牌號6`和`車牌號7`分別需要等到車庫開出兩輛車空出兩個車位後才停進去。這就體現了`Semaphore` 的`acquire` 方法如果沒有獲取到憑證它就會阻塞,而`tryAcquire`方法如果沒有獲取到憑證不會阻塞的。
# semaphore在dubbo中的應用
在`Dubbo`中可以給`Provider`配置執行緒池大小來控制系統提供服務的最大並行度,預設是`200`。