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

Java 併發編程精髓:線程協作機制——條件隊列與同步工具類全解析

     編輯:Java 綜合教學

Java 併發編程:線程協作的藝術——條件隊列與同步工具類

線程間的交互不僅限於互斥(鎖),更多時候需要協作:生產者-消費者模型、任務編排、資源池管理等。Java 提供了內置條件隊列(Object.wait/notify)、顯式條件隊列(Lock + Condition)以及高層同步工具類(BlockingQueue、CountDownLatch、Semaphore、CyclicBarrier)來優雅地解決協作問題。

一、內置條件隊列:Object.wait/notify/notifyAll

每個 Java 對象天然擁有一把內置鎖(Monitor)和一個內置條件隊列。

核心 API

  • wait():釋放當前鎖,線程進入 WAITING 狀態,直到被 notify/notifyAll 喚醒或中斷,喚醒後重新競爭鎖
  • notify():喚醒條件隊列中任意一個等待線程
  • notifyAll():喚醒條件隊列中所有等待線程,只有一個能獲取鎖繼續執行

使用鐵律

  1. 必須在同步塊/方法中調用(持有對象鎖),否則拋出 IllegalMonitorStateException
  2. 標準範式:while (條件不滿足) wait(); 防止虛假喚醒與通知遺漏

通知遺漏問題與解決

若通知線程在等待線程調用 wait() 之前執行 notify(),等待線程將永久阻塞。解決方案:引入狀態變量配合 while 循環:

// 等待線程
synchronized (lock) {
    while (!okToProceed) {
        lock.wait();
    }
    // 執行任務...
}

// 通知線程
synchronized (lock) {
    okToProceed = true; // 先更新狀態
    lock.notifyAll();   // 再通知
}

即使通知發生在等待之前,狀態變量已為 true,等待線程檢查條件後直接繼續執行,避免阻塞。

二、顯式條件隊列:Lock + Condition

ReentrantLock 的 newCondition() 可創建多個獨立的 Condition 對象,每個對應一種等待條件(如「非滿」、「非空」),功能更強大:

  • 每把鎖可擁有多個條件隊列
  • 支援可中斷等待(await() 拋出 InterruptedException)、超時等待(awaitNanos)、公平/非公平隊列
  • 對應方法:await() / signal() / signalAll()

經典範例:有界緩衝區

public class ConditionBoundBuffer {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull  = lock.newCondition(); // 緩衝區非滿
    private final Condition notEmpty = lock.newCondition(); // 緩衝區非空
    private final T[] items = (T[]) new Object[100];
    private int tail, head, count;

    public void put(T x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) notFull.await();  // 等待非滿
            items[tail] = x;
            if (++tail == items.length) tail = 0;
            ++count;
            notEmpty.signal(); // 喚醒消費者
        } finally { lock.unlock(); }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) notEmpty.await(); // 等待非空
            T x = items[head];
            items[head] = null;
            if (++head == items.length) head = 0;
            --count;
            notFull.signal(); // 喚醒生產者
            return x;
        } finally { lock.unlock(); }
    }
}

三、java.util.concurrent 同步工具類

高層工具類封裝了底層同步細節,開箱即用,降低併發編程難度。

3.1 BlockingQueue:阻塞隊列——生產者消費者首選

線程安全、內部用 ReentrantLock + Condition 實現阻塞/喚醒,支援可中斷操作。

實現類特點適用場景
ArrayBlockingQueue數組、有界、FIFO固定大小緩衝區,如線程池任務隊列
LinkedBlockingQueue鏈表、可選有界、FIFO高吞吐生產消費,預設無界(Integer.MAX_VALUE)
PriorityBlockingQueue優先級排序、無界任務優先級調度
SynchronousQueue零容量、直接移交線程間直接數據傳遞,CachedThreadPool 內部使用

核心方法:put(e)(滿則阻塞)、take()(空則阻塞)、offer(e)/poll()(非阻塞)、poll(timeout)(超時)。

3.2 CountDownLatch:倒計時閉鎖——等待多任務完成

「一扇門,需按 N 次才能開」。主線程 await() 等待,N 個工作線程完成後各自 countDown(),計數歸零門開啟,所有等待線程釋放。

CountDownLatch latch = new CountDownLatch(N);
// 工作線程
latch.countDown();
// 主線程
latch.await(); // 阻塞直到計數為 0

典型場景:應用啟動等待多個初始化任務、並行計算聚合結果、壓測並發起點同步。

3.3 Semaphore:信號量——控制併發訪問量

「資源池許可證」。初始許可數 = 可用資源數。acquire() 獲取許可(無則阻塞),release() 歸還許可。

Semaphore sem = new Semaphore(10); // 10 個資源
sem.acquire(); // 佔用一個
try { /* 使用資源 */ } finally { sem.release(); } // 歸還

應用:數據庫連接池、限流器、控制併發任務數。0-1 信號量可作互斥鎖。

3.4 CyclicBarrier:循環柵欄——多線程齊點同步

「所有人到齊再出發」。N 個線程各自執行至 barrier.await() 阻塞,最後一個到達觸發可選的 Runnable 任務,然後所有線程同時繼續。柵欄可重複使用(reset())。

CyclicBarrier barrier = new CyclicBarrier(N, () -> System.out.println("全員到齊,開始比賽"));
// 每個線程
barrier.await(); // 等待其他線程

區別:CountDownLatch 等待事件(計數歸零),CyclicBarrier 等待線程(全員到達),且可循環。

選型指南

  • 生產者-消費者、任務隊列 → BlockingQueue
  • 主線程等待多子任務完成 → CountDownLatch
  • 限制資源併發訪問數 → Semaphore
  • 多線程分階段同步、迭代計算 → CyclicBarrier
  • 需要精細控制等待條件、多條件協作 → Lock + Condition

總結

掌握條件隊列與同步工具類,是編寫高性能、可維護併發程序的關鍵。優先使用高層工具類(BlockingQueue 等),它們經過專家優化、測試充分;僅在工具類無法滿足需求時,才降級使用 Lock/Condition 或 wait/notify。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved