天天看點

10.JUC 鎖- CountDownLatch

基本概念

CountDownLatch 這個類能夠使一個或多個線程等待其他線程完成各自的工作後再執行。

例如,應用程式的主線程希望在負責啟動架構服務的線程已經啟動所有的架構服務之後再執行。

10.JUC 鎖- CountDownLatch

調用方法

假設有勞工、老闆兩種角色。勞工負責工作,老闆負責檢查勞工已完成的工作。那麼存在以下條件:老闆必須等待勞工完成工作後才能進行檢查。

  • 勞工
public class Worker implements Runnable {
    private CountDownLatch cdl;
    private String name;

    public Worker(CountDownLatch cdl, String name) {
        this.cdl = cdl;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            System.out.println(name + " 開始工作 ...");
            Thread.sleep(); 
            System.out.println(name + " 結束工作...");
            cdl.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
           
  • 老闆
// 老闆類
class Boss implements Runnable {
    private CountDownLatch cdl;
    private String name;

    public Boss(CountDownLatch cdl, String name) {
        this.cdl = cdl;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            cdl.await();
            System.out.println(name + " 檢查工作 ...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
           
  • 調用過程
public class Test {
    public static void main(String[] args) {
        final CountDownLatch cdl = new CountDownLatch();
        new Thread(new Boss(cdl, "大老闆")).start();
        new Thread(new Worker(cdl, "勞工甲")).start();
        new Thread(new Worker(cdl, "勞工乙")).start();
    }
}
           
  • 輸出結果
勞工甲 開始工作 ...
勞工乙 開始工作 ...
勞工甲 結束工作...
勞工乙 結束工作...
大老闆 檢查工作 ...
           

内部構造

  • 構造函數,CountDownLatch 在建構時需要指定計數值(count)。
// 内部類
private final Sync sync;

public CountDownLatch(int count) {
    if (count < ) {
        // 抛出異常...
    }
    this.sync = new Sync(count);
}
           
  • 同步器,CountDownLatch 中定義了一個繼承自 AQS 的 Sync,并用 AQS 的狀态值表示計數值。
Sync(int count) {
    setState(count);
}
           
  • 計數值,即 count,類似于 ReetrantLock 的重入計數。隻有在 count 為 0 時,才能觸發釋放鎖的操作。

countDown

遞減操作,調用該方法的線程會遞減 CountDownLatch 的計數值。

// 遞減鎖的計數(若計數到達零,則釋放所有等待的線程)
public void countDown() {
    sync.releaseShared();
}
           

調用過程如下:

AQS.releaseShared->CountDownLatch.tryReleaseShared->AQS.doReleaseShared

重點來看 tryReleaseShared 方法,該方法在 sync 作具體實作:

public boolean tryReleaseShared(int releases) {

    for (;;) {
        // 校驗計數值
        int c = getState();
        if (c == ) {
            return false;
        }

        // 遞減計數值,若計數為 0,則喚醒所有通過 await 操作加入同步等待隊列的線程
        int nextc = c - ;
        if (compareAndSetState(c, nextc)) {
            return nextc == ;
        }
    }
}
           

await

等待操作,調用該方法的線程會進入阻塞狀态,直到 CountDownLatch 的計數值為 0 時,該線程才會被喚醒。

public void await() throws InterruptedException {
    // 擷取共享鎖,并且不忽略 
    sync.acquireSharedInterruptibly();
}
           

調用過程如下:

AQS.acquireSharedInterruptibly ->CountDownLatch.tryAcquireShared ->AQS.doAcquireSharedInterruptibly

重點來看 tryAcquireShared 方法,該方法在 sync 作具體實作:

// Sync
public int tryAcquireShared(int acquires) {
    // count 為 0 表示成功擷取鎖
    return getState() ==  ?  : -;
}
           

總結

CountDownLatch 内部采用了共享鎖來實作。

  • count :計數值可以了解為通過鎖的重入計數。
  • countDown :表示釋放鎖,每次操作都會遞減鎖的重入計數,隻有重入計數為 0 時,才能成功釋放鎖,否則傳回 false。
  • await :表示擷取鎖,隻有在重入計數為 0 時,才可以成功擷取鎖,否則進入阻塞狀态。