[java併發程式設計]基於訊號量semaphore實現限流器

字母哥部落格發表於2022-05-17

一、什麼是訊號量

“訊號量”在程式設計術語中使用單詞semaphore,那什麼是“訊號量”?訊號量就好比你家廚房入口架子上擺了三把鍋。

  • 如果你的孩子熱奶拿走一把,你的老婆熱湯拿走一把,你的媽媽做菜拿走一把,你想煮麵條就沒有鍋了。當你看到這種情況,你就不會進入廚房了,你處於等待狀態。也就說廚房按照“鍋的數量”作為訊號量,只能容納三個人(執行緒)。
  • 當你的老婆熱完湯之後,把鍋重新放回架子上,你就可以去獲得一個鍋,你就可以進入廚房了。

二、訊號量類Semaphore

通過上文的介紹,我們可以總結出訊號量的重要組成部分

  • 計數器:計算訊號量的使用情況,鍋(訊號)被使用一次減1,鍋(訊號)被還回一次加1
  • 等待佇列:當任務數量大於訊號量數量上限的時候,任務進入等待佇列

訊號量在JDK中是由 java.util.concurrent.Semaphore 實現的,Semaphore提供了兩個建構函式。permits引數代表訊號量的數量(鍋的數量),fair代表訊號量的獲取是否遵循公平原則。所謂的公平原則就是:先啟動的執行緒先呼叫semaphore.acquire();方法,就先得到一個訊號“鍋”(permit),遵循先來後到的原則。

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為例,實現基於訊號量的限流。

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把鍋                  => 備註:用完依次釋放

歡迎關注我的部落格,更多精品知識合集

本文轉載註明出處(必須帶連線,不能只轉文字):字母哥部落格 - zimug.com

覺得對您有幫助的話,幫我點贊、分享!您的支援是我不竭的創作動力!。另外,筆者最近一段時間輸出瞭如下的精品內容,期待您的關注。

相關文章