Semaphore 自白:限流器用我就對了!

作者 | 王磊

出品 | Java 中文社羣

基本信息

專業技能

我的專業技能就是 “管理證書”,使用此技能可以輕鬆的實現「限流」功能

什麼是限流?

比如五一小長假快到了,到那時會有大量的人去各個景區遊玩,但是每個景區能容納的人是有限的,比如大西安的大唐芙蓉園,它的日承載量是 6 萬人次,也就是說每天最多能讓 6 萬來這裏遊玩,但五一的時候會來很多的人,比如突然來了 10 萬人,那這個時候就只能「限流」排隊等待入園了。

也就說,大唐芙蓉園會讓 6 萬人先進去玩,剩餘的人在門口等待排隊,當有人從裏面出來的時候,才允許另一個排隊的人進去。工作人員會把人數始終控制在 6 萬人以下,這樣做的目的是爲了讓遊玩的人有一個好的體驗,不至於造成一些意外事故,比如踩踏事件什麼的,一定程度上保證了社會的穩定,也便於景區良好的口碑建立和日後的正常運營,而這種排隊限制最大人數的行爲就是「限流」

再來舉個例子,比如以車輛的限號來說,它也是限流的一種常見場景。這樣做的好處,一方面是可以保護環境儘可能少一些碳排放,另一方面能有效的緩解上、下班高峯時段的擁堵情況。尤其是在大西安,很難想象如果不限號,那麼會堵成什麼樣?(PS:讓原本本不富裕的生活更是雪上加霜...)

咱們再從生活中的事例回到程序當中,假設一個程序只能爲 10W 人提供服務,突然有一天因爲某個熱點事件,造成了系統短時間內的訪問量迅速增加到了 50W,那麼導致的直接結果是系統崩潰,任何人都不能用系統了,顯然只有少人數能用遠比所有人都不能用更符合我們的預期,因此這個時候我們要使用「限流」了。

使用 Semaphore 實現限流

Semaphore 在創建的時候可以設置證書的數量,相當於設置了限流的最大值,再通過 release() 方法來發放證書,通過 acquire() 方法來阻塞並等待證書,這樣就通過控制證書的方式來實現限流功能了。

項目經驗

接下來,咱們使用代碼的方式來演示 Semaphore 的使用。我們以停車場的限流爲例,假設整個停車場只有 2 個車位(車位雖少,但足矣說明問題),但來停車的卻有 5 輛車,顯然車位不夠用了,此時需要保證停車場最多隻能有 2 輛車,接下來咱們使用 Semaphore 來實現車輛的限流功能,具體實現代碼如下:

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * Author:磊哥
 * By:Java中文社羣
 */
publicclass SemaphoreExample {
    // 創建信號量
    static Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {

        // 創建 5 個固定的線程數
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        // 定義執行任務
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 拿到當前線程的名稱
                String tname = Thread.currentThread().getName();
                System.out.println(String.format("老司機:%s,停車場外排隊,時間:%s",
                        tname, new Date()));
                try {
                    // 執行此行,讓所有線程先排隊等待進入停車場
                    Thread.sleep(100);
                    // 執行阻塞
                    semaphore.acquire();
                    System.out.println(String.format("老司機:%s,已進入停車場,時間:%s",
                            tname, new Date()));
                    Thread.sleep(1000);
                    System.out.println(String.format("老司機:%s,離開停車場,時間:%s",
                            tname, new Date()));
                    // 釋放鎖
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        // 執行任務 1
        threadPool.submit(runnable);

        // 執行任務 2
        threadPool.submit(runnable);

        // 執行任務 3
        threadPool.submit(runnable);

        // 執行任務 4
        threadPool.submit(runnable);

        // 執行任務 5
        threadPool.submit(runnable);

        // 等線程池任務執行完之後關閉
        threadPool.shutdown();
    }
}

以上代碼的執行結果如下:

從上述的結果我們可以看出,當有 5 輛車同時需要進入停車場時,因爲停車場的停車位只有 2 個,所以停車場最多隻能容納 2 輛車。此時我們通過 Semaphore 的 acquire 方法(阻塞等待)和 release 方法(頒發一個證書)順利的實現了限流的功能,讓停車場的車輛數始終控制在 2 輛車以下(等於或小於 2 輛車)。

個人評價

我(Semaphore)實現證書控制手段有兩種,一種公平模式和非公平模式,當然爲了執行的性能考慮,默認情況下我採取的是非公平的方式,具體實現可見源碼:

public Semaphore(int permits) {
    sync = new NonfairSync(permits); // 非公平模式
}

關於公平模式和非公平模式

所謂的公平模式就是以調用 acquire() 的先後順序來決定獲取許可證的順序的,公平模式遵循先進先出(FIFO)原則;而非公平模式是搶佔式的,也就是有可能一個新的獲取線程恰好在一個許可證釋放時得到了這個許可證,而前面還有等待的線程。

顯然使用非公平的模式性能更高,因爲它會把許可證發放給剛好準備好的線程,而不用再根據先後順序去 “叫號” 了。

使用公平模式

當然,你可以手動選擇使用公平模式來運行 Semaphore,Semaphore 提供了兩個構造函數,源碼如下:

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

如果想用公平模式就可以使用第二個構造函數 Semaphore(int permits, boolean fair),將 fair 值設置爲 true 就是公平模式來獲取證書了。

其他補充

我還提供了一些其他方法,用於實現更多的功能,詳情如下:

總結

Semaphore 信號量是用來管理一組證書的,默認情況下它採取的是非公平的方式來管理證書,這樣做的目的是爲了實現高性能。Semaphore 中包含了兩個重要的方法:release() 方法發佈一個許可證書;acquire() 方法阻塞並等待一個證書。當線程調用了 acquire() 方法只有擁有了證書才能繼續執行,因此可以使用 Semaphore 來實現限流。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/fXOlppLJbKfQhKAVyPu6ug