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

Java 並發編程深度解析:阻塞隊列原理、實現與生產消費者實戰

     編輯:Java 相關知識

什麼是阻塞隊列?

隊列是先進先出(FIFO)的數據結構,基本操作:尾部入隊、頭部出隊。阻塞隊列在普通隊列基礎上增加了線程阻塞語義:

  • 隊列為空時,take() 獲取元素操作阻塞,直到有元素入隊;
  • 隊列滿時,put() 添加元素操作阻塞,直到有空位。

這完美契合「生產者-消費者」模型,無需額外實現同步與喚醒策略。

JDK 主流阻塞隊列對比

隊列底層結構有界/無界特點
ArrayBlockingQueue數組有界(構造時指定容量)默認非公平鎖,可選公平鎖
LinkedBlockingQueue鏈表有界(默認 Integer.MAX_VALUE)吞吐量通常優於數組隊列
PriorityBlockingQueue堆(數組)無界按優先級排序,非 FIFO
DelayQueue優先隊列無界元素延遲到期才能取出,生產者永不阻塞

核心 API

方法行為阻塞性
put(E e)尾部插入,滿則阻塞等待阻塞
take()頭部獲取並移除,空則阻塞等待阻塞
offer(E e, timeout, unit)尾部插入,滿則等待指定時間,超時返回 false超時阻塞
poll(timeout, unit)頭部獲取,空則等待指定時間,超時返回 null超時阻塞

ArrayBlockingQueue 源碼深度解析

核心字段

final Object[] items;      // 底層數組
int takeIndex;             // 隊頭索引
int putIndex;              // 隊尾索引
int count;                 // 元素數量
final ReentrantLock lock;  // 重入鎖
private final Condition notEmpty; // 非空條件
private final Condition notFull;  // 非滿條件

put() 入隊流程

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();           // 可中斷加鎖
    try {
        while (count == items.length)   // 滿則等待
            notFull.await();
        enqueue(e);                     // 實際入隊
    } finally {
        lock.unlock();
    }
}

private void enqueue(E x) {
    final Object[] items = this.items;
    items[putIndex] = x;
    if (++putIndex == items.length) putIndex = 0; // 環形數組
    count++;
    notEmpty.signal();                  // 喚醒消費者
}

take() 出隊流程

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)              // 空則等待
            notEmpty.await();
        return dequeue();               // 實際出隊
    } finally {
        lock.unlock();
    }
}

private E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex];
    items[takeIndex] = null;            // GC 友好
    if (++takeIndex == items.length) takeIndex = 0;
    count--;
    notFull.signal();                   // 喚醒生產者
    return x;
}

設計亮點:單鎖雙條件變量(notEmpty/notFull)、環形數組避免數據移動、可中斷鎖響應中斷。

實戰:阻塞隊列實現生產者-消費者

public class ProducerConsumerDemo {
    private static final int QUEUE_SIZE = 10;
    private static final ArrayBlockingQueue queue = new ArrayBlockingQueue<>(QUEUE_SIZE);

    public static void main(String[] args) {
        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
    }

    static class Producer extends Thread {
        @Override public void run() {
            while (true) {
                try {
                    queue.put(1); // 生產
                    System.out.println("生產元素,剩餘空間:" + (QUEUE_SIZE - queue.size()));
                    Thread.sleep(500);
                } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }
    }

    static class Consumer extends Thread {
        @Override public void run() {
            while (true) {
                try {
                    queue.take(); // 消費
                    System.out.println("消費元素,剩餘元素:" + queue.size());
                    Thread.sleep(1000);
                } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }
    }
}

輸出示例:

生產元素,剩餘空間:9
生產元素,剩餘空間:8
...
消費元素,剩餘元素:9
生產元素,剩餘空間:9
...

避坑指南與最佳實踐

  • 容量規劃:有界隊列必須指定合理容量,防止 OOM;
  • 公平性選擇:高併發、低延遲場景優先非公平鎖(默認),需嚴格 FIFO 時選公平鎖;
  • 優雅關閉:使用 offer/poll 帶超時版本,配合關閉標誌位,避免線程無限阻塞;
  • 監控指標:關注隊列大小、剩餘容量、阻塞時長,及時發現背壓。

總結

阻塞隊列是 Java 並發包中解耦生產消費的核心組件。ArrayBlockingQueue 以環形數組+單鎖雙條件實現高效有界緩衝,LinkedBlockingQueue 適合高吞吐場景,PriorityBlockingQueue/DelayQueue 則服務於調度類業務。掌握其原理與 API,能讓你在高併發系統設計中遊刃有餘。

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