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

Java 並發核心:阻塞隊列完整指南 - 原理、實現、生產者消費者模式與企業級應用

     編輯:Java 程式設計入門

阻塞隊列核心概念

BlockingQueue 是支援兩種額外原子操作的隊列:

  • 插入阻塞:隊列滿時,生產者執行緒阻塞直到有空位
  • 移除阻塞:隊列空時,消費者執行緒阻塞直到有元素

自動平衡生產/消費速率,實現生產者-消費者模式的最佳工具。

四組核心 API 行為對比

操作拋出異常返回特殊值一直阻塞超時退出
插入add(e)offer(e)put(e)offer(e, time, unit)
移除remove()poll()take()poll(time, unit)
檢查element()peek()--

行為詳解

  • 拋出異常:違反隊列容量限制立即失敗
  • 特殊值:offer 返回 boolean,poll/peek 返回 null
  • 阻塞:put/take 無限等待,可響應中斷
  • 超時:指定時間內嘗試,超時返回 false/null

JDK 七大阻塞隊列實現詳解

隊列底層結構邊界鎖機制特點適用場景
ArrayBlockingQueue環狀數組有界單一 ReentrantLock公平/非公平可選,固定大小固定緩衝區、高併發簡單場景
LinkedBlockingQueue單向鏈表有界(預設 Integer.MAX_VALUE)雙鎖 (takeLock/putLock)生產消費並行,吞吐高高併發、大吞吐、無界風險需注意
PriorityBlockingQueue優先隊列(堆)無界單一 ReentrantLock元素需 Comparable/Comparator,僅阻塞 take優先級任務、定時調度
DelayQueuePriorityQueue無界ReentrantLock元素實現 Delayed,到期才能取出緩存過期、定時任務、重試延遲
LinkedBlockingDeque雙向鏈表有界單一鎖雙端操作,減少競爭工作竊取、雙端緩衝
LinkedTransferQueue鏈表無界CAS + 自旋transfer() 直接移交,tryTransfer() 非阻塞嘗試高併發移交、訊號傳遞
SynchronousQueue無緩衝節點無界(容量 0)CAS + 雙棧/雙隊列直接移交,無存儲,公平/非公平模式執行緒池直接提交、高吞吐移交

核心實現深度解析

ArrayBlockingQueue:單鎖環狀數組

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull = lock.newCondition();
}
  • 單一鎖控制 put/take,簡單但限制併發度
  • Condition 實現精確喚醒:notEmpty.await()、notFull.signal()
  • 公平鎖:FIFO 順序獲取鎖,吞吐較低但避免飢餓

LinkedBlockingQueue:雙鎖鏈表

  • putLock 保護尾節點插入,takeLock 保護頭節點移除
  • 生產/消費真正並行,高併發吞吐優於 ArrayBlockingQueue
  • 無界預設值 Integer.MAX_VALUE,生產快於消費時 OOM 風險
  • Node 物件頻繁創建/回收,GC 壓力較大

SynchronousQueue:無緩衝直接移交

  • 不存儲元素,生產者直接把元素移交給消費者
  • JDK 6+ 採用 雙棧/雙隊列 + CAS 無鎖算法,效能極高
  • 公平模式:FIFO 隊列 + 公平鎖;非公平模式:LIFO 棧 + 非公平鎖(預設)
  • Executors.newCachedThreadPool() 內部使用

生產者-消費者實戰範例

檔案搜尋:一生產者 N 消費者

public class FileSearcher {
    public static void main(String[] args) {
        BlockingQueue<File> queue = new ArrayBlockingQueue<>(100);
        File rootDir = new File("/var/log");
        String keyword = "ERROR";
        int consumerCount = 10;
        
        // 生產者:遞歸枚舉檔案
        new Thread(() -> {
            try {
                enumerateFiles(rootDir, queue);
                // 投放毒丸結束信號
                for (int i = 0; i < consumerCount; i++) queue.put(POISON_PILL);
            } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }).start();
        
        // 消費者:搜尋關鍵字
        for (int i = 0; i < consumerCount; i++) {
            new Thread(() -> {
                try {
                    while (true) {
                        File file = queue.take();
                        if (file == POISON_PILL) {
                            queue.put(POISON_PILL); // 傳遞給下一個消費者
                            break;
                        }
                        searchFile(file, keyword);
                    }
                } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }).start();
        }
    }
    
    static final File POISON_PILL = new File("");
    
    static void enumerateFiles(File dir, BlockingQueue<File> queue) throws InterruptedException {
        File[] files = dir.listFiles();
        if (files == null) return;
        for (File f : files) {
            if (f.isDirectory()) enumerateFiles(f, queue);
            else queue.put(f);
        }
    }
    
    static void searchFile(File file, String keyword) {
        try (Scanner scanner = new Scanner(file)) {
            int lineNum = 0;
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                if (line.contains(keyword)) {
                    System.out.printf("%s:%d %s%n", file.getPath(), ++lineNum, line);
                }
            }
        } catch (IOException e) { /* 忽略 */ }
    }
}

DelayQueue 緩存過期實戰

public class CacheEntry implements Delayed {
    private final String key;
    private final long expireTime; // 納秒級絕對時間
    
    public CacheEntry(String key, long ttlMillis) {
        this.key = key;
        this.expireTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(ttlMillis);
    }
    
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
    }
    
    @Override
    public int compareTo(Delayed o) {
        long diff = this.expireTime - ((CacheEntry)o).expireTime;
        return (diff > 0) ? 1 : (diff < 0) ? -1 : 0;
    }
    
    public String getKey() { return key; }
}

// 清理執行緒
new Thread(() -> {
    while (!Thread.interrupted()) {
        try {
            CacheEntry entry = delayQueue.take(); // 阻塞直到過期
            cache.remove(entry.getKey());
            log.info("清理過期緩存: {}", entry.getKey());
        } catch (InterruptedException e) { break; }
    }
}).start();

效能對比與選型指南

場景推薦隊列理由
固定大小緩衝、簡單生產消費ArrayBlockingQueue預分配數組,無 GC 壓力,行為可預測
高併發、大吞吐、生產消費速率接近LinkedBlockingQueue雙鎖並行,吞吐最高,但需指定容量防 OOM
優先級任務、定時調度PriorityBlockingQueue堆排序,自然支援優先級
延遲執行、緩存過期、重試退避DelayQueue基於 PriorityQueue,到期自動可取
工作竊取、雙端操作LinkedBlockingDeque雙端減少競爭,ForkJoinPool 內部使用
高併發直接移交、執行緒池提交SynchronousQueue無緩衝,直接移交,吞吐極高
非阻塞嘗試移交、超時控制LinkedTransferQueuetryTransfer/transfer 靈活控制

最佳實踐與避坑指南

  1. 無界隊列必須指定容量:new LinkedBlockingQueue<>(10000) 防 OOM
  2. 毒丸模式優雅關閉:特殊元素通知消費者退出,而非 interrupt()
  3. 異常處理在任務內部:防止任務異常導致執行緒死亡,影響後續任務
  4. 避免在任務中長時間阻塞:IO/網路操作提交到專用執行緒池
  5. 監控隊列大小:queue.size()、queue.remainingCapacity() 接入監控系統
  6. 公平鎖慎用:除非嚴格需要 FIFO,否則非公平鎖吞吐更高
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved