首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java CountDownLatch 原理深度解析(下):countDown() 與釋放共享鎖

     編輯:Java 綜合教學

Java CountDownLatch 原理深度解析(下):countDown() 與釋放共享鎖

承接上篇 await() 分析,本文深入剖析 countDown() 如何透過 AQS 共享模式遞減狀態並喚醒等待執行緒。

一、countDown() 呼叫鏈

public void countDown() {
    sync.releaseShared(1); // 呼叫 AQS 共享釋放
}

二、AQS.releaseShared(int arg)

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) { // 嘗試釋放共享資源
        doReleaseShared();       // 喚醒後繼節點
        return true;
    }
    return false;
}

三、Sync.tryReleaseShared(int releases) —— 核心邏輯

protected boolean tryReleaseShared(int releases) {
    for (;;) { // 自旋保證 CAS 成功
        int c = getState(); // 獲取當前計數
        if (c == 0) return false; // 已歸零,無需釋放
        int nextc = c - 1; // 遞減
        if (compareAndSetState(c, nextc)) { // CAS 更新狀態
            return nextc == 0; // 歸零返回 true,觸發 doReleaseShared
        }
        // CAS 失敗則繼續自旋
    }
}

關鍵點:state 從 N 遞減至 0,只有當遞減後為 0 時才返回 true,通知 AQS 執行喚醒流程。

四、doReleaseShared() —— 喚醒等待佇列

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                // 頭節點處於等待喚醒狀態
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue;
                unparkSuccessor(h); // 喚醒後繼節點
            } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
                // 傳播共享狀態
                continue;
            }
        }
        if (h == head) break; // 頭節點未變更,結束
    }
}

此方法將同步佇列頭節點狀態設為 PROPAGATE,確保共享釋放訊號向後傳播,喚醒所有因 await() 掛起的執行緒。

五、await() 端的自旋與喚醒

等待執行緒在 doAcquireSharedInterruptibly 中自旋檢查:

final Node p = node.predecessor();
if (p == head) {
    int r = tryAcquireShared(arg); // state == 0 返回 1
    if (r >= 0) {
        setHeadAndPropagate(node, r); // 設為新頭節點並傳播
        return;
    }
}

setHeadAndPropagate 同樣呼叫 doReleaseShared(),形成雙重保障機制。

六、完整範例:主執行緒等待 5 個工作執行緒完成

public class TestCountDownLatch {
    private static final int TASK_NUM = 5;

    static class MyRunnable implements Runnable {
        private final int num;
        private final CountDownLatch latch;
        MyRunnable(int num, CountDownLatch latch) { this.num = num; this.latch = latch; }

        @Override
        public void run() {
            System.out.println("第 " + num + " 個執行緒開始執行任務...");
            try { Thread.sleep(5000); } catch (InterruptedException ignored) {}
            System.out.println("第 " + num + " 個執行緒任務結束...");
            latch.countDown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(TASK_NUM);
        for (int i = 1; i <= TASK_NUM; i++) {
            new Thread(new MyRunnable(i, latch)).start();
        }
        System.out.println("主執行緒等待其他執行緒完成...");
        latch.await();
        System.out.println("所有任務完成,主執行緒繼續執行...");
    }
}

七、優缺點總結

優點缺點
API 簡單,單一計數器即可協調多執行緒計數器不可重置,無法重複使用
基於 AQS 共享鎖,效能優異不支援動態新增任務
釋放與獲取語義清晰,適合「一等多」場景若需循環等待,請改用 CyclicBarrier 或 Phaser
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved