在 Java 集合框架中,Queue 介面與 List、Set 同層級,皆繼承自 Collection 介面。LinkedList 實作了 Queue 介面,當變數型別宣告為 Queue 時,僅能存取 Queue 定義的方法,無法直接呼叫 LinkedList 特有的非 Queue 方法,這屬於介面導向程式設計的封裝特性。此外,BlockingQueue 繼承自 Queue,提供了支援阻塞操作的佇列功能,是多執行緒協作的核心工具。
佇列是一種典型的先進先出 (FIFO) 資料結構,具備兩個基本操作:在尾部新增元素、在頭部移除元素。在多執行緒環境下,若嘗試向已滿的阻塞佇列添加元素,或從空的阻塞佇列移除元素,執行緒將會阻塞。這種特性使得阻塞佇列成為生產者-消費者模式的理想選擇:工作者執行緒可將中間結果存入佇列,其他執行緒取出並處理,佇列會自動平衡雙方處理速度的差異,避免資源浪費。
JDK 1.5 引入的阻塞佇列操作可根據回應方式分為三類:
| 類別 | 方法 | 行為描述 | 異常/回傳值 |
|---|---|---|---|
| 拋出異常 | add(e) | 新增元素 | 佇列滿拋出 IllegalStateException |
remove() | 移除並回傳頭部元素 | 佇列空拋出 NoSuchElementException | |
element() | 檢視頭部元素(不移除) | 佇列空拋出 NoSuchElementException | |
| 回傳特殊值 | offer(e) | 新增元素 | 佇列滿回傳 false |
poll() | 移除並回傳頭部元素 | 佇列空回傳 null | |
peek() | 檢視頭部元素(不移除) | 佇列空回傳 null | |
| 阻塞等待 | put(e) | 新增元素 | 佇列滿時阻塞 |
take() | 移除並回傳頭部元素 | 佇列空時阻塞 |
注意: poll() 與 peek() 在失敗時回傳 null,因此佇列不允許插入 null 值,否則無法區分「佇列為空」與「取出的元素為 null」。
除了上述方法,BlockingQueue 尚提供帶超時參數的 offer 與 poll:
boolean success = queue.offer(element, 100, TimeUnit.MILLISECONDS);
Object head = queue.poll(100, TimeUnit.MILLISECONDS);
若在指定時間內無法完成操作,則回傳 false 或 null,不會無限期阻塞。
Integer.MAX_VALUE(視為無界),建構時可指定容量轉為有界。fair=true,採用 ReentrantLock 公平鎖,等待最久的執行緒優先獲取;但公平性會降低吞吐量,僅在極度需要時啟用。Comparator 排序,非 FIFO。PriorityQueue),put 不會阻塞,但記憶體耗盡可能拋出 OutOfMemoryError。take() 在佇列空時阻塞。Comparable 或提供 Comparator。PriorityQueue 實作。Delayed 介面(含 getDelay(TimeUnit) 與 compareTo)。poll() 回傳 null;不允許 null 元素。以下範例示範如何使用 BlockingQueue 協調「檔案掃描執行緒(生產者)」與「內容搜尋執行緒(消費者)」,展現無需額外同步、自動負載平衡的優勢。
import java.io.File;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class FileKeywordSearch {
private static final int SEARCH_THREADS = 4;
private static final String POISON_PILL = "POISON_PILL";
public static void main(String[] args) {
BlockingQueue<File> queue = new LinkedBlockingQueue<>();
AtomicInteger matchCount = new AtomicInteger(0);
String keyword = "targetKeyword";
File rootDir = new File("C:/data");
// 生產者:遞迴掃描檔案
Thread producer = new Thread(() -> {
scanFiles(rootDir, queue);
// 發送毒丸訊號結束消費者
for (int i = 0; i < SEARCH_THREADS; i++) {
try { queue.put(new File(POISON_PILL)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
});
// 消費者:搜尋關鍵字
Thread[] consumers = new Thread[SEARCH_THREADS];
for (int i = 0; i < SEARCH_THREADS; i++) {
consumers[i] = new Thread(() -> {
while (true) {
try {
File file = queue.take();
if (POISON_PILL.equals(file.getName())) break;
if (searchInFile(file, keyword)) {
System.out.println("Found in: " + file.getAbsolutePath());
matchCount.incrementAndGet();
}
} catch (InterruptedException e) { Thread.currentThread().interrupt(); break; }
}
});
consumers[i].start();
}
producer.start();
try { producer.join(); for (Thread t : consumers) t.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
System.out.println("Total matches: " + matchCount.get());
}
private static void scanFiles(File dir, BlockingQueue<File> queue) {
File[] files = dir.listFiles();
if (files == null) return;
for (File f : files) {
if (f.isDirectory()) scanFiles(f, queue);
else if (f.isFile() && f.getName().endsWith(".txt")) {
try { queue.put(f); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; }
}
}
}
private static boolean searchInFile(File file, String keyword) {
// 實際讀檔搜尋邏輯略
return true;
}
}
LinkedBlockingQueue(高吞吐緩衝)、ArrayBlockingQueue(固定記憶體、公平性)、PriorityBlockingQueue(優先級處理)、DelayQueue(定時觸發)。offer/poll/peek 避免異常,或 put/take 實現阻塞同步,搭配超時版本增強健壯性。