Java NIO (New I/O) 是 JDK 1.4 引入的嶄新 I/O API,我們習慣稱之為「非阻塞 I/O」。在 JDK 1.4 之前,Java 僅支援 BIO (Blocking I/O),即傳統的阻塞式 I/O 模型。BIO 雖然使用直觀,但在高併發場景下存在明顯短板:每個連接都需要獨佔一個執行緒,導致資源消耗巨大、擴展性差、上下文切換開銷高。
在探討 NIO 之前,必須釐清四個容易混淆的概念:
關鍵區別在於:同步/異步關注的是「誰負責數據拷貝」(用戶態還是內核態),阻塞/非阻塞關注的是「進程在等待數據就緒階段的狀態」。
Linux 將所有外部設備視為文件操作,讀寫返回文件描述符。對於網絡 I/O 的 read 操作,涉及兩個階段:
五種模型正是在這兩個階段的處理方式上有所不同:
Linux 默認所有 socket 都是阻塞的。用戶進程調用 recvfrom 後,在兩個階段均被阻塞:先等內核準備數據,再等數據拷貝完成。特點是簡單但併發能力弱。
通過設置 socket 為 non-blocking。調用 read 時,若數據未就緒,內核立即返回 error (EWOULDBLOCK/EAGAIN),進程不阻塞,可繼續執行其他任務,稍後再輪詢調用 read。數據就緒時,read 會阻塞進程進行拷貝。缺點:進程需不斷主動輪詢,CPU 佔用率高。
即 select、poll、epoll。單進程可同時處理多個連接。原理:進程調用 select/epoll 阻塞等待,內核監視所有註冊的 socket,任一就緒則返回,進程再調用 read 拷貝數據。雖然需兩次系統調用,但優勢在於高併發連接處理能力,而非單連接速度。Linux 普遍選用 epoll,解決了 select 的文件描述符數量限制、線性掃描性能瓶頸等問題。
用戶進程發起 aio_read 後立即返回,完全不阻塞。內核自行完成數據準備、拷貝到用戶內存,完成後發送信號通知進程。整個過程進程零阻塞,無需輪詢,無需主動拷貝。
| 模型 | 數據準備階段 | 數據拷貝階段 | 歸類 |
|---|---|---|---|
| Blocking I/O | 阻塞 | 阻塞 | Synchronous |
| Non-blocking I/O | 不阻塞(返回錯誤) | 阻塞 | Synchronous |
| I/O Multiplexing | 阻塞於 select/epoll | 阻塞 | Synchronous |
| Asynchronous I/O | 不阻塞 | 不阻塞(內核完成) | Asynchronous |
核心結論:
理解了底層模型,Java NIO 的三大核心組件就容易理解了:
epoll/select,單線程管理多個 Channel 的就緒事件。下一篇將詳細講解 Channel、Buffer、Selector 的實戰用法與代碼示例。