Java基礎--CountDownLatch--計數器鎖(門閂鎖)

@toc

1. CountDownLatch

一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。

用給定的計數 初始化 CountDownLatch。由於調用了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。之後,會釋放所有等待的線程,await 的所有後續調用都將立即返回。這種現象只出現一次——計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier。

1.1 CountDownLatch 的UML圖

在這裏插入圖片描述

1.2 CountDownLatch 的屬性方法

在這裏插入圖片描述

2. CountDownLatch 構造

在這裏插入圖片描述
如果傳入的值小於0,那麼拋出異常。
否則,初始化內部的Sync

2.1 Sync 構造

在這裏插入圖片描述
CountDownLatch內部的Sync也是繼承於AQS的。
Sync的構造,傳入的值是初始化AQS的鎖持有線程數量的。

3. await

在這裏插入圖片描述
CountDownLatch的await方法是等待通知,將當前線程阻塞,直到鎖空閒。(或者說計數器倒數至0)
AQS的acquireSharedInterruptibly方法請看
Java基礎–AQS原理
的5.6.4小節。
在這裏插入圖片描述
調用AQS的acquireSharedInterruptibly方法。
await方法響應中斷,而且計數器鎖是一個共享鎖。
是共享鎖,就需要AQS的子類Sync實現tryAcquireShared和tryReleaseShared方法。
在這裏插入圖片描述
在自旋中嘗試獲取共享鎖,嘗試獲取共享鎖,調用的是CountDownLatch的Sync實現的tryAcquireShared方法。
在這裏插入圖片描述
只有鎖空閒,才允許等待競爭隊列中的線程執行。
換句話說,當計數器倒數沒到0時,線程需要等待計數器歸0.

4. await(long,TimeUnit)

帶有超時時間的等待,響應中斷。
在這裏插入圖片描述
調用了AQS的tryAcquireSharedNanos方法。
AQS的tryAcquireSharedNanos方法請看
Java基礎–AQS原理
的5.6.5小節。
在這裏插入圖片描述
帶有超時的等待,也是調用CountDownLatch的Sync實現的tryAcquireShared方法的。

5. countDown

計數器鎖值減1.
在這裏插入圖片描述
直接調用AQS的releaseShared方法。
AQS的releaseShared方法請看
Java基礎–ReentrantReadWriterLock–重入讀寫鎖
的2.1.5小節。
在這裏插入圖片描述
AQS的releaseShared方法會調用AQS子類實現的tryReleaseShard方法的。
也就是CountDownLatch的Sync實現的tryReleaseShared方法。
在這裏插入圖片描述
自旋將鎖持有數量減1,也就是將計數器鎖的值減1.
如果鎖狀態已經是0,表示現在已經無法繼續減下去了。不過也不會拋出異常,因爲countDown是沒有返回值的。
如果鎖狀態不是0,那麼將鎖狀態減1,最後返回鎖是否空閒。
如果鎖空閒,那麼等待競爭隊列中的線程都可以再次調用tryAcquireShared方法嘗試獲取鎖。
在這裏插入圖片描述
此時鎖空閒,鎖狀態爲0,也就是,等待競爭隊列中每一個線程都能獲取鎖。
請注意,CountDownLatch的計數器值爲0之後,在無法恢復的。
CountDownLatch只能做減法。

6. getCount

獲取當前計數器鎖的值。即獲取鎖狀態值。
在這裏插入圖片描述
CountDownLatch調用其內部的Sync的getCount方法。
在這裏插入圖片描述
CountDownLatch的Sync的getCount方法調用AQS的getState方法。

7. 示例

public class MyCountDownLatch {

    public static void main(String[] args) {
        // 創建一個值爲count的計數器鎖
        int count = 10;
        CountDownLatch countDownLatch = new CountDownLatch(count);
        Runnable runnable = () -> {
            Thread thread = Thread.currentThread();
            System.out.print(thread.getName() + " start count down : " + countDownLatch.getCount() + " -> ");
            countDownLatch.countDown();
            System.out.println(countDownLatch.getCount() + " time is " + System.currentTimeMillis());
        };

        Runnable runnable1 = () -> {
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName() + " await count down latch , time is " + System.currentTimeMillis());
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                System.out.println("ie");
            }
            System.out.println(thread.getName() + " await count down latch end, time is " + System.currentTimeMillis());
        };

        for (int i = 0; i < count; i++) {
            new Thread(runnable1, "awaiter" + i).start();
        }
        for (int i = 0; i < count; i++) {
            new Thread(runnable, "countDown" + i).start();
        }
        System.out.println("main end");
    }

}

執行結果
在這裏插入圖片描述
開始後,await線程都被阻塞了,然後count down線程將計數器鎖的值減小,直到爲0.
此時await線程就都被喚醒繼續執行了。

需要注意一點,在輸出中 ->左邊可能相等,但是右邊一定不相等。
因爲右邊使用cas進行設置的,保證線程安全。

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