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

Java 並發核心:阻塞隊列全家族深度解析與生產者消費者實戰

     編輯:Java 綜合教學

Java 並發核心:阻塞隊列全家族深度解析與生產者消費者實戰

java.util.concurrent.BlockingQueue 是構建高併發生產消費系統的基石。本文系統梳理 7 大核心實現類、四種操作語義、內部同步機制差異,並提供文件搜索、緩存淘汰、定時調度三大實戰場景完整代碼。

四大操作語義對照表

語義插入方法移除方法行為特徵
拋出異常add(e)remove()隊列滿/空時拋 IllegalStateException/NoSuchElementException
返回特殊值offer(e)poll()失敗返回 false/null,不阻塞
一直阻塞put(e)take()直到成功或線程中斷,生產消費核心 API
超時退出offer(e, t, u)poll(t, u)超時返回 false/null,支援背壓控制

七大核心實現類特性矩陣

隊列底層結構邊界鎖策略適用場景
ArrayBlockingQueue數組有界單鎖(可公平)固定緩衝區、高吞吐、公平調度
LinkedBlockingQueue鏈表有界(默認 Integer.MAX_VALUE)雙鎖(take/put 分離)高併發生產消費、默認首選
PriorityBlockingQueue堆無界單公平鎖優先級任務調度、不阻塞生產者
DelayQueue堆無界單鎖定時任務、緩存過期、訂單超時
LinkedBlockingDeque雙向鏈表有界雙鎖工作竊取、雙端操作
LinkedTransferQueue鏈表無界CAS+自旋直接傳遞、低延遲交換
SynchronousQueue無緩衝0 容量CAS+雙棧/雙隊列線程池直接提交、高吞吐交換

深度剖析:LinkedBlockingQueue 雙鎖設計

// 核心字段
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();

// take() 只鎖 takeLock
public E take() throws InterruptedException {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) notEmpty.await();
        return dequeue();
    } finally { takeLock.unlock(); }
}

// put() 只鎖 putLock
public void put(E e) throws InterruptedException {
    final ReentrantLock putLock = this.putLock;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) notFull.await();
        enqueue(e);
    } finally { putLock.unlock(); }
}

優勢: 生產者/消費者並行無競爭,高併發下吞吐量遠超單鎖 ArrayBlockingQueue。

風險: 無界模式下生產者過快導致 OOM,生產環境必須指定容量。

SynchronousQueue:零拷貝直接交換

  • 無內部緩衝,put 與 take 配對完成數據直接遞交
  • 公平模式(FIFO 隊列)vs 非公平模式(LIFO 棧,默認)
  • Executors.newCachedThreadPool() 內部使用非公平模式
  • 吞吐量 > LinkedBlockingQueue > ArrayBlockingQueue

實戰場景一:多線程文件關鍵字搜索(生產者-消費者)

public class FileKeywordSearch {
    private static final int QUEUE_SIZE = 100;
    private static final int SEARCH_THREADS = 50;
    private static final File POISON = new File(""); // 終止信號

    public static void main(String[] args) throws InterruptedException {
        BlockingQueue queue = new ArrayBlockingQueue<>(QUEUE_SIZE);
        
        // 生產者:遞歸枚舉文件
        new Thread(() -> {
            try { enumerate(new File("/var/log"), queue); 
                  queue.put(POISON); } 
            catch (InterruptedException ignored) {}
        }).start();

        // 消費者:搜索關鍵字
        for (int i = 0; i < SEARCH_THREADS; i++) {
            new Thread(() -> {
                try {
                    while (true) {
                        File file = queue.take();
                        if (file == POISON) { queue.put(POISON); break; }
                        search(file, "ERROR");
                    }
                } catch (IOException | InterruptedException e) { Thread.currentThread().interrupt(); }
            }).start();
        }
    }

    static void enumerate(File dir, BlockingQueue queue) throws InterruptedException {
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) enumerate(f, queue);
            else queue.put(f);
        }
    }
    static void search(File file, String keyword) throws IOException { ... }
}

關鍵技巧: 使用 POISON 對象傳遞終止信號,消費者收到後重新放回以通知其他消費者。

實戰場景二:DelayQueue 實現緩存過期淘汰

public class CacheEntry implements Delayed {
    private final String key;
    private final long expireTime; // 納秒時間戳
    
    public CacheEntry(String key, long ttlMs) {
        this.key = key;
        this.expireTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(ttlMs);
    }
    
    @Override public long getDelay(TimeUnit unit) {
        return unit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
    }
    @Override public int compareTo(Delayed o) {
        return Long.compare(this.expireTime, ((CacheEntry)o).expireTime);
    }
}

// 淘汰線程
new Thread(() -> {
    while (!Thread.interrupted()) {
        try {
            CacheEntry expired = delayQueue.take(); // 阻塞直到最近條目過期
            cacheMap.remove(expired.key);
        } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }
}).start();

優勢: 無需定時掃描,take() 精準阻塞到最近過期時間,CPU 占用極低。

實戰場景三:SynchronousQueue 構建高吞吐線程池

// 核心線程數 0,最大無界,隊列 SynchronousQueue
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
    new SynchronousQueue<>(),
    new ThreadFactoryBuilder().setNameFormat("worker-%d").build(),
    new ThreadPoolExecutor.AbortPolicy()
);
// 任務提交即創建線程,空閒 60s 回收,適合突發型高併發

選型決策樹

需要優先級? → PriorityBlockingQueue
需要延時執行? → DelayQueue
需要雙端操作/工作竊取? → LinkedBlockingDeque
追求極致吞吐/直接交換? → SynchronousQueue / LinkedTransferQueue
通用高併發生產消費? → LinkedBlockingQueue (指定容量!)
固定緩衝/需公平調度? → ArrayBlockingQueue (公平模式)

阻塞隊列不只是容器,更是並發協調協議。理解每種隊列的鎖策略、內存模型、背壓機制,才能在高併發系統中做出正確架構決策。

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