Java中的Semaphore如何使用

簡介

semaphore中文意思既是信號量,它的主要功能就是用來控制某個資源同時被訪問的線程數。

為瞭控制某塊資源的並發訪問量時,可以使用Semaphore對象中的acquire()方法獲取訪問令牌,如果Semaphore對象訪問令牌已發完,那麼當前獲取令牌的線程將會進入阻塞,帶其他線程進行release()釋放令牌時,當前線程才有機會獲得令牌從而擁有訪問權限。

簡述實現原理

Semaphore實際上是一種共享鎖,因為它允許多個線程並發獲取共享的資源。在Semaphore對象創建時必須設置可用令牌的初始數量permits,用於控制並發時同時獲取資源權限的線程數量。在Semaphore類中繼承瞭同步隊列AbstractQueuedSynchronizer,在此類中有個屬性state用於標記當前並發的隊列數,也就是獲取令牌的線程數,那麼在進行acquire()的時候,就會嘗試獲取共享鎖,獲取鎖成功後state值將加1,如果state值已經達到permits時就表示令牌已派發完,當前線程將進入阻塞狀態,待其他線程進行release()state值將減1,此時就會從隊列中獲取頭部線程進行喚醒讓其獲得令牌進行資源訪問。

如下圖,仔細查看源碼就會發現Semaphore實際上就是重寫瞭AbstractQueuedSynchronizer中的tryAcquireShared()tryReleaseShared()方法來實現的。

方法介紹

  • Semaphore重載瞭兩個構造函數,其一是Semaphore(int permits)直接指定令牌數,默認為非公平鎖;其二是Semaphore(int permits,boolean fair),fair參數即表示線程搶占令牌的公平性,true為公平鎖,否則為非公平鎖。
  • acquire()無參表示默認獲取一個令牌,acquire(int permits)表示獲取指定permits數量的令牌數,如果令牌不夠,則當前線程進入阻塞狀態。

tryAcquire()無參表示嘗試獲取一個令牌,該方法是非阻塞的,所以如果令牌數不夠獲取失敗返回false,否則就返回true;同時也重載瞭方法tryAcquire(int permits)指定獲取令牌數,tryAcquire(int permits, long timeout, TimeUnit unit)在有效時間內嘗試獲取指定數量的令牌數,如果超時仍未獲取到令牌則返回false,否則返回true。 release同上支持無參與帶參指定釋放令牌數的方法。 drainPermits()獲取剩下的可用令牌。 hasQueuedThread()用於判斷當前Semaphare實例中是否存在等待獲取令牌的線程。

案例分析

分析一下下面這段簡短的代碼,首先是創建一個信號量為5的Semaphore對象,然後在創建一個線程池(這裡為瞭演示方便,實際開發中不建議使用此線程池創建),利用for循環並發運行100個線程,當線程運行時優先獲取一個令牌,在線程中的業務代碼裡我們做瞭1秒的休眠,為瞭展示等待獲取令牌的效果,在延遲1秒執行完業務代碼時進行令牌釋放,後續的線程才能逐個被喚醒獲取令牌訪問共享資源。

@Slf4j
public class SemaphoreDemo {

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(5);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    log.info("成功獲取令牌");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    log.info("釋放令牌");
                    semaphore.release();
                }
            });
        }
        executorService.shutdown();
    }
}
  • 運行結果

由下圖運行結果可見,100個線程並發並不是一次性都執行完的,而是要等待前面的線程釋放令牌後等待的線程才可以獲取令牌進行業務代碼的運行。

適用場景

Semaphore主要是運用在多線程環境中對某一些共享資源的訪問量限制,防止多個線程並發訪問同一資源,可能會導致大多數線程獲取資源時都需要進行加鎖,那如果是獲取數據庫中的數據,那麼就可以緩解數據庫的壓力。

另一種情況是用於多線程運行的一個流量限制,一般情況下我們可能會通過線程池做一步線程數的控制,但是某些業務為瞭減輕CPU的負擔,還是會做一些同時運行線程數的限制。

到此這篇關於Java中的Semaphore如何使用的文章就介紹到這瞭,更多相關Semaphore使用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: