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

Java 並發容器深度解析:BlockingQueue 家族全景圖

     編輯:Java 相關知識

前言:為什麼需要並發隊列?

在多線程生產者-消費者模型中,傳統集合配合 wait/notify 實現同步既繁瑣又易出錯。JDK 5.0 引入 java.util.concurrent 包,提供了線程安全、高效能的並發容器。本文聚焦 BlockingQueue 及其核心實現類,剖析源碼級設計細節。

BlockingQueue 體系總覽

BlockingQueue 類結構圖

核心實現類:

  • ArrayBlockingQueue:有界數組隊列,單鎖雙條件
  • LinkedBlockingQueue:可選有界鏈表隊列,雙鎖分離
  • PriorityBlockingQueue:無界優先級隊列,單鎖
  • DelayQueue:延遲隊列,基於 PriorityQueue
  • LinkedBlockingDeque:雙端阻塞隊列

BlockingQueue 接口契約

BlockingQueue 方法分類表

四組方法對應不同阻塞語義:

操作拋出異常返回布爾值無限阻塞超時阻塞
插入add(e)offer(e)put(e)offer(e, time, unit)
移除remove()poll()take()poll(time, unit)
檢查element()peek()--

ArrayBlockingQueue:有界數組隊列深度剖析

核心字段

final Object[] items;      // 存儲元素的數組
int takeIndex;             // 下一次取元素的索引
int putIndex;              // 下一次放元素的索引
int count;                 // 當前元素數量
final ReentrantLock lock;  // 獨佔鎖(公平/非公平可選)
private final Condition notEmpty;  // 非空條件
private final Condition notFull;   // 非滿條件

三種插入方法對比

// 1. offer(e) - 非阻塞,滿則返回 false
public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length) return false;
        insert(e);
        return true;
    } finally {
        lock.unlock();
    }
}

// 2. put(e) - 阻塞直到有空位
public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            notFull.await();  // 釋放鎖並進入等待
        insert(e);
    } finally {
        lock.unlock();
    }
}

// 3. offer(e, timeout, unit) - 定時阻塞
public boolean offer(E e, long timeout, TimeUnit unit) 
        throws InterruptedException {
    // ... 使用 notFull.awaitNanos(nanos) 實現超時等待
}

環形數組機制

private void insert(E x) {
    items[putIndex] = x;
    putIndex = inc(putIndex);  // 環形增長:(++i == items.length) ? 0 : i
    ++count;
    notEmpty.signal();  // 喚醒一個等待取元素的線程
}

private E extract() {
    final E[] items = this.items;
    E x = items[takeIndex];
    items[takeIndex] = null;  // GC 友好
    takeIndex = inc(takeIndex);
    --count;
    notFull.signal();  // 喚醒一個等待放元素的線程
    return x;
}

Condition 實現原理

Condition 依賴 AbstractQueuedSynchronizer 內部類 ConditionObject。await() 流程:

  1. 創建條件等待節點加入等待隊列
  2. 完全釋放鎖(fullyRelease)
  3. LockSupport.park 掛起線程
  4. 被 signal() 喚醒後,重新競爭鎖(acquireQueued)

這保證了 await/signal 必須在鎖保護下執行,享受重入特性。

LinkedBlockingQueue:雙鎖分離的鏈表隊列

  • 默認容量 Integer.MAX_VALUE(無界)
  • 兩把鎖:putLock(寫)、takeLock(讀)
  • 兩個條件:notFull、notEmpty
  • 需同時加鎖的操作(remove、toArray 等)按固定順序 putLock → takeLock 避免死鎖

PriorityBlockingQueue:優先級無界隊列

private final PriorityQueue<E> q;      // 底層優先級隊列
private final ReentrantLock lock = new ReentrantLock(true);  // 公平鎖
private final Condition notEmpty = lock.newCondition();
  • 內部封裝 PriorityQueue,自然順序或自定義 Comparator
  • 單鎖同步所有操作
  • 隊列滿時自動擴容(grow()),無 notFull 條件

DelayQueue:延遲任務調度隊列

元素必須實現 Delayed 接口(繼承 Comparable<Delayed>)。核心特性:

  • 只能取出已過期元素:getDelay(NANOSECONDS) <= 0
  • 隊首為最早過期的元素
  • poll() 返回 null 而非阻塞;take() 阻塞直到有元素過期
  • size() 包含未過期元素
  • 單一 Condition available 僅服務 take/poll

應用場景:ScheduledThreadPoolExecutor 內部使用 DelayQueue 管理定時任務。

BlockingDeque 與 LinkedBlockingDeque:雙端擴展

  • BlockingDeque 繼承 BlockingQueue 和 Deque
  • LinkedBlockingDeque 單鎖(lock)雙條件(notEmpty、notFull)
  • 首尾均可操作,適合工作竊取隊列

避坑指南:批量操作非原子性

所有實現類同時實現 Collection 接口,但 addAll、removeAll、retainAll、containsAll 等批量操作不保證原子性。例如向 ArrayBlockingQueue addAll 10 個元素,可能中途失敗但已部分入隊。解決方案:自行加鎖或使用單元素操作循環。

總結選型建議

場景推薦實現關鍵特性
固定容量、高並發生產消費ArrayBlockingQueue環形數組、單鎖、公平性可選
大容量/無界、吞吐優先LinkedBlockingQueue雙鎖分離讀寫、鏈表動態擴容
優先級處理、任務調度PriorityBlockingQueue堆排序、無界、自動擴容
定時/延遲任務執行DelayQueue時間輪思想、過期才可取
雙端操作、工作竊取LinkedBlockingDeque首尾並行、單鎖
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved