Java基礎--Semaphore--計數信號量鎖

1. Semaphore 簡介

一個計數信號量。從概念上講,信號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然後再獲取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可對象,Semaphore 只對可用許可的號碼進行計數,並採取相應的行動。
Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的線程數目。

1.1 Semaphore 的 UML 結構

在這裏插入圖片描述
Semaphore和ReentrantLock的結構相同。
都是內部的Sync繼承了AQS,內部還有FairSync和NonfairSync繼承了Sync。

1.2 Semaphore的屬性和方法

在這裏插入圖片描述

2. Semaphore 的構造

在這裏插入圖片描述

2.1 Samephore(int)

創建指定信號量的,不公平的信號量鎖。
在這裏插入圖片描述
根據傳入的值,調用不公平的信號量同步鎖。

2.2 Samephore(int,boolean)

在這裏插入圖片描述
根據傳入的boolean值,選擇是否是公平的處理方式。
然後調用FairSync或者是NonfaireSync類的構造。

3. Semaphore 的方法

接下來看下Semaphore提供的方法。

3.1 acquire

在這裏插入圖片描述
直接調用AQS的accquireSharedInterruptibly,傳入1.
在這裏插入圖片描述
AQS的acquireSharedInterruptibly方法會調用AQS子類實現的tryAcquireShared方法。

3.2 acquire(int)

在這裏插入圖片描述
如果請求的數量小於0,那麼拋出參數異常。
否則調用AQS的acquireSharedInterruptibly方法。
最終還是調用Sync的nonfairTryAcquireShared和FairSync的tryAcquireShared方法。

3.3 acquireUninterruptibly

在這裏插入圖片描述
調用AQS的acquireShared方法。
在這裏插入圖片描述
AQS的acquireShared方法會調用子類實現的tryAcquireShared方法。
最終調用的還是Sync的nonfairTryAcquireShared和FairSync的tryAcquireShared方法。

3.4 acquireUninterruptibly(int)

在這裏插入圖片描述
嘗試獲取共享鎖,不響應中斷。獲取請求的資源。
如果請求的資源數量小於0,那麼拋出參數異常。
如果參數校驗通過,調用就AQS的acquireShared方法。
最終調用的還是Sync的nonfairTryAcquireShared和FairSync的tryAcquireShared方法。

3.5 availablePermits

返回此信號量中當前可用的許可數。
在這裏插入圖片描述
調用Sync的getPermits方法。
在這裏插入圖片描述
直接返回鎖狀態(資源現在空閒的數量)

3.6 drainPermits

獲取並返回立即可用的所有許可。
在這裏插入圖片描述
調用Sync的drainPermits方法
在這裏插入圖片描述
強制將鎖狀態設置爲0.即可用資源數量爲0.

3.7 getQueuedThreads

返回一個 collection,包含可能等待獲取的線程。
在這裏插入圖片描述
調用AQS的getQueuedThreads方法

3.8 getQueueLength

返回正在等待獲取的線程的估計數目。
在這裏插入圖片描述

3.9 hasQueuedThreads

查詢是否有線程正在等待獲取。
在這裏插入圖片描述
調用的是AQS的hasQueuedThreads方法
在這裏插入圖片描述
直接判斷等待競爭隊列是否爲空。

3.10 isFair

如果此信號量的公平設置爲 true,則返回 true。
在這裏插入圖片描述
根據全局的Sync的對象,判斷sync對象是否是FairSync的實例對象。

3.11 reducePermits

根據指定的縮減量減小可用許可的數目。(減少可用資源數量)
在這裏插入圖片描述
如果傳入的指定的縮減量小於0,那麼拋出參數異常。
通過參數校驗後,調用Sync的reducePermits方法

3.12 release

釋放一個許可,將其返回給信號量。(釋放資源,將可用的資源數量增加)
在這裏插入圖片描述
調用AQS的releaseShared方法
在這裏插入圖片描述
調用的是AQS的子類實現的tryReleaseShared方法。
也就是Semaphore的Sync的tryReleaseShared方法。

3.13 release(int)

釋放指定個許可,將其返回給信號量。(釋放資源,將可用的資源數量增加)
在這裏插入圖片描述
先進行參數校驗,如果釋放的資源個數小於0,那麼拋出參數異常。(你不能打着還錢的幌子借錢)
然後調用的是AQS的realeaseShared方法。
和3.12相同,最終調用的是Semaphore的Sync的tryReleaseShared方法。

3.14 tryAcquire

從此信號量獲取一個許可,在提供這些許可前一直將線程阻塞,或者線程已被中斷。 (簡單來說就是借錢)
在這裏插入圖片描述
調用的是Semaphore的Sync的nonfairTryAcquireShared方法。
如果調用Sync的nonfairTryAcquireShared方法成功後,返回目前可用的資源數量,大於0表示獲取成功。
(你向地主借錢,不能把地主借的地主負債了,地主也不會自己借錢然後在借給你)

3.15 tryAcquire(int)

從此信號量獲取給定數目的許可,在提供這些許可前一直將線程阻塞,或者線程已被中斷。 (簡單來說就是借錢)
在這裏插入圖片描述
先會進行參數校驗,如果請求的資源數量小於0,那麼拋出參數異常。
(沒有會打着借錢的幌子給你錢)
調用的也是Sync的nonfairTryAcquireShared方法。

3.16 tryAcquire(int,long,TimeUnit)

如果在給定的等待時間內此信號量有可用的所有許可,並且當前線程未被中斷,則從此信號量獲取給定數目的許可。
在這裏插入圖片描述
第一個一定是參數校驗,參數都不合法,後面也沒有繼續的必要了。
調用的是AQS的tryAcquireSharedNanos方法。
最終調用的也是FairSync或者NonfairSync的tryAcquireShared方法

3.17 tryAcquire(long,TimeUnit)

如果在給定的等待時間內此信號量有可用的所有許可,並且當前線程未被中斷,則從此信號量獲取給定數目的許可。
在這裏插入圖片描述

4. Semaphore 的Sync

4.1 Sync的構造

在這裏插入圖片描述
設置鎖狀態爲傳入的值。

4.2 nonfairTryAcquireShared

// 嘗試獲取共享鎖
final int nonfairTryAcquireShared(int acquires) {
	// 自旋
    for (;;) {
    	// 獲取鎖狀態(現有資源數量)
        int available = getState();
        // 鎖狀態減去請求的數量(剩餘資源數量)
        int remaining = available - acquires;
        if (remaining < 0 || // 鎖狀態小於0(剩餘資源數量小於0,表示不夠請求的數量)
            compareAndSetState(available, remaining)) // 如果夠,則更新鎖狀態(設置剩餘資源數量)
           	// 返回鎖狀態(剩餘資源數量)
            return remaining;
    }
}

4.3 reducePermits

// 以指定的縮減量減少可用資源數量
final void reducePermits(int reductions) {
	// 開始自旋
    for (;;) {
    	// 獲取現有的可用的資源數量(鎖狀態)
        int current = getState();
        // 計算出減去請求後的可用資源數量(鎖狀態)
        int next = current - reductions;
        // 如果減完發現大於原有值(傳入值是負數),那麼拋出錯誤(因爲前面已經進行參數校驗了,此時參數小於0,確實是錯誤,而不是異常)
        if (next > current) // underflow
            throw new Error("Permit count underflow");
        // 使用CAS進行更新鎖狀態
        if (compareAndSetState(current, next))
        	// 更新成功直接結束,否則進行自旋
            return;
    }
}

4.4 tryReleaseShared

釋放資源。將資源歸還給信號量鎖。

// 嘗試釋放共享鎖
protected final boolean tryReleaseShared(int releases) {
	// 進入自旋
    for (;;) {
    	// 獲取當期鎖狀態(可用的資源數量)
        int current = getState();
        // 當前資源數量加上釋放的資源數量(可用資源增加(歸還))
        int next = current + releases;
        // 做檢測,如果歸還完反而可用資源變少 ,那麼就是發生錯誤
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        // 使用cas更新鎖狀態(可用資源)
        if (compareAndSetState(current, next))
        	// 返回釋放成功(歸還成功)
            return true;
    }
}

5. Semaphore 的FairSync

5.1 FairSync 構造

在這裏插入圖片描述
調用父類的構造方法,傳入permits.

5.2 tryAcquireShared

// 嘗試獲取共享鎖
protected int tryAcquireShared(int acquires) {
	// 自旋
    for (;;) {
    	// 當前線程前面是否還有等待的線程
        if (hasQueuedPredecessors())
        	// 如果前面還有線程在等待,那麼當前線程嘗試獲取失敗
            return -1;
        // 獲取鎖狀態
        int available = getState();
        // 鎖狀態與請求數量的差值(看看是不是夠)
        int remaining = available - acquires;
        if (remaining < 0 || // 差值小於0,表示不夠。即使將現有的全部給你,也不夠
            compareAndSetState(available, remaining)) // 如果夠,則將可用的數量減去請求的個數
            return remaining; // 返回剩餘的個數
    }
}

獲取當前線程在等待競爭隊列中有沒有前繼節點。
簡單來說,就是獲取當前線程前面還有沒有等待線程。
在這裏插入圖片描述
如果等待競爭隊列不爲空,那麼頭結點的後繼節點爲空或者等待線程不是當前現場,那麼就表示當前線程前面還有等待的線程。
(不會存在head != tail && head.next == null)

6. Semaphore 的NonfairSync

6.1 NonfairSync的構造

在這裏插入圖片描述
調用父類的構造方法,傳入permits.

6.2 tryAcquireShared

在這裏插入圖片描述
直接調用Sync的nonfairTryAcquireShared方法。

7. AQS實現的方法

7.1 getQueuedThreads

獲取等待競爭的線程集合。(不準確,因爲等待競爭隊列是試試變化的)

// 獲取等待競爭隊列的線程集合
public final Collection<Thread> getQueuedThreads() {
	// 構造返回集合對象
    ArrayList<Thread> list = new ArrayList<Thread>();
    // 從等待競爭隊列的尾節點開始遍歷,只要線程節點不爲空,那麼就加入返回集合中。
    for (Node p = tail; p != null; p = p.prev) {
        Thread t = p.thread;
        if (t != null)
            list.add(t);
    }
    // 返回線程集合
    return list;
}

7.2 getQueueLength

獲取等待競爭的線程的數量

// 獲取等待競爭的線程的數量
public final int getQueueLength() {
	// 初始化數量
    int n = 0;
    // 從等待競爭隊列的尾節點開始遍歷,只要線程節點不爲空,那麼就將線程數量++
    for (Node p = tail; p != null; p = p.prev) {
        if (p.thread != null)
            ++n;
    }
    // 返回線程數量
    return n;
}

7.3 tryAcquireShharedNanos

// 嘗試獲取共享鎖,有超時時間,響應中斷
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) // arg = 1
        throws InterruptedException {
    // 獲取線程中斷標誌,並重置中斷標誌
    if (Thread.interrupted())
    	// 如果線程已經中斷,那麼直接拋出中斷異常,快速結束
        throw new InterruptedException();
    // 否則就會調用tryAcquireShared 和 doAcquireSharedNanos方法
    return tryAcquireShared(arg) >= 0 ||
        doAcquireSharedNanos(arg, nanosTimeout);
}

tryAcquireShared方法請看3.2
doAcquireSharedNanos請看
Java基礎–AQS原理
的5.6.2.4和5.6.5小節

8. Semaphore 示例

我們以地主爲例。
假設村子裏有一個地主,他有100元。
村子裏面每個人都需要用錢進行交易,有些人足夠,有些人則不夠。
足夠的人可以順利完成交易。
不夠的人需要向地主借錢完成交易。

public class MySemaphore {

    public static void main(String[] args) {
    	// 定義資源總量爲100
        int count = 100;
        // 創建公平的信號量鎖
        Semaphore semaphore = new Semaphore(count, true);
        Runnable runnable = () -> {
        	// 獲取當前線程的線程名字
            String name = Thread.currentThread().getName();
            try {
            	// 隨機生成當前線程需要的數量
                int need = (int) (Math.random() * 100);
                // 阻塞獲取資源
                semaphore.acquire(need);
                // 等到資源後輸出剩餘資源數量
                System.out.println(name + " get " + need + "\t, now semaphore have " + semaphore.availablePermits());
                // 線程佔用資源一定的是時間
                Thread.sleep((long) (Math.random() * 10000)); // 睡眠10秒內
                // 釋放資源
                semaphore.release(need);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        for (int i = 0; i < 10; i++) {
        	// 創建線程
            new Thread(runnable, "村名" + i).start();
        }
    }

}

執行結果
在這裏插入圖片描述
從執行結果中可以看出村名5和村名1是一起去借錢的。因爲借完錢後,剩餘的錢相同。
同理,村名3和村名2也是一起去的。
如果我們將每個村民的需要的錢數設置爲10以內。
也就是地主完全有錢。
在這裏插入圖片描述
在這裏插入圖片描述
因爲每個村名借錢的錢數都滿足,所以不存在線程等待的問題,所以看起來就很順滑。

9. 總結

Semaphore是一個很有用的同步輔助類,但是感覺更多的使用在資源管理等方面。
看了這麼多的同步類:
ReentrantLock
ReentrantReadWriteLock
CountDownLatch
Cyclicbarrier
個人感覺:
ReentrantLock適合大多數的普遍的線程同步,對於讀多還是寫多不確定時。
ReentrantReadWriteLock適合較高性能要求的線程同步,較爲適合讀多寫少的場景。
CountDownLatch適合倒計時類的線程同步,而且達到條件後,做的操作是一次性的那種。
CyclicBarrier適合做線程間統一切面處理的操作,一批線程,所有線程都到達指定的點後,做一個預定的操作,然後各個線程在繼續運行。
Semaphore則適合資源管理,線程佔用資源,就將可用資源減少,線程歸還資源,就將可用資源增加。

我感覺,在理解Semaphore的時候,結合借錢還錢可能比較好理解。<~^v^~>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章