JDK 1.4 引入 NIO (New I/O) 提供非阻塞通道與多路複用能力,但 Servlet API 基於阻塞 I/O 語義設計,兩者天然衝突。本文展示如何透過 生產者/消費者模式 + Piped Streams 橋接差異,實現基於 Servlet 的 NIO 伺服器,在海量長連接場景下大幅超越傳統阻塞 I/O 模型。
| 維度 | 傳統阻塞 I/O | NIO 非阻塞 I/O |
|---|---|---|
| 線程模型 | 一連接一線程 | 固定線程多路複用 |
| 讀取語義 | 阻塞直到數據就緒 | 立即返回,可能零字節/部分數據 |
| Servlet 相容 | 原生支援 | 需適配層轉換 |
┌─────────────────────────────────────────────────────────────┐
│ NIO Selector Loop (單線程) │
│ select() → 接受新連接 / 讀取可用數據 → 寫入 PipedOutputStream │
└─────────────────────────┬───────────────────────────────────┘
│ PipedInputStream (緩衝管道)
▼
┌─────────────────────────────────────────────────────────────┐
│ Worker Thread Pool (固定線程池) │
│ 從管道讀取阻塞流 → 構建 HttpRequest/HttpResponse → 呼叫 Servlet │
└─────────────────────────────────────────────────────────────┘
關鍵創新:java.io.PipedInputStream/PipedOutputStream 將非阻塞通道數據「緩衝轉換」為 Servlet 所需的阻塞 InputStream,主線程專職多路複用,工作線程專職 Servlet 處理,互不干擾。
public void listen() {
SelectionKey key = null;
try {
while (true) {
selector.select(); // 阻塞等待事件
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
key = (SelectionKey) it.next();
handleKey(key); // 處理 Accept/Read 事件
it.remove();
}
}
} catch (IOException e) {
key.cancel();
}
}
// 自定義佇列支援線程等待/通知
public class Queue extends LinkedList {
private int waitingThreads = 0;
public synchronized void insert(Object obj) {
addLast(obj);
notify(); // 喚醒等待線程
}
public synchronized Object remove() {
if (isEmpty()) {
try { waitingThreads++; wait(); } // 佇列空則阻塞
catch (InterruptedException e) { Thread.interrupted(); }
waitingThreads--;
}
return removeFirst();
}
public boolean isEmpty() {
return (size() - waitingThreads <= 0);
}
}
// 核心邏輯:將 Channel 數據寫入 PipedOutputStream
// 工作線程從 PipedInputStream 讀取,構建 HttpRequest
// 關鍵點:管道緩衝防止主線程阻塞,協議層處理訊息邊界
public void run() {
while (true) {
Client client = (Client) myQueue.remove(); // 從佇列取請求
try {
for (;;) {
HttpRequest req = new HttpRequest(client.clientInputStream, myServletContext);
HttpResponse res = new HttpResponse(client.key);
defaultServlet.service(req, res); // 標準 Servlet 處理
if (client.notifyRequestDone()) break; // Keep-Alive 檢查
}
} catch (Exception e) {
client.key.cancel();
client.key.selector().wakeup();
}
}
}


AsyncContext,原生支援非阻塞處理這個生產者/消費者 + 管道流模式,不僅解決了 Servlet API 與 NIO 的相容性難題,更展示了「固定線程池處理無限連接」的架構智慧,是高併發伺服器設計的經典範本。