常見的 IO 模型有哪些?Java 中 BIO、NIO、AIO 的區(qū)別?
IO 模型這塊確實挺難理解的,需要太多計算機底層知識。寫這篇文章用了挺久,就非常希望能把我所知道的講出來吧!希望朋友們能有收貨!為了寫這篇文章,還翻看了一下《UNIX 網(wǎng)絡(luò)編程》這本書,太難了,我滴乖乖!心痛~
個人能力有限。如果文章有任何需要補充/完善/修改的地方,歡迎在評論區(qū)指出,共同進步!
相關(guān)閱讀(原創(chuàng)):淘寶一面:“說一下 Spring Boot 自動裝配原理唄?”?
前言
I/O 一直是很多小伙伴難以理解的一個知識點,這篇文章我會將我所理解的 I/O 講給你聽,希望可以對你有所幫助。
I/O
何為 I/O?
I/O(Input/Outpu) 即輸入/輸出 。
我們先從計算機結(jié)構(gòu)的角度來解讀一下 I/O。
根據(jù)馮.諾依曼結(jié)構(gòu),計算機結(jié)構(gòu)分為 5 大部分:運算器、控制器、存儲器、輸入設(shè)備、輸出設(shè)備。

輸入設(shè)備(比如鍵盤)和輸出設(shè)備(比如鼠標)都屬于外部設(shè)備。網(wǎng)卡、硬盤這種既可以屬于輸入設(shè)備,也可以屬于輸出設(shè)備。
輸入設(shè)備向計算機輸入數(shù)據(jù),輸出設(shè)備接收計算機輸出的數(shù)據(jù)。
從計算機結(jié)構(gòu)的視角來看的話, I/O 描述了計算機系統(tǒng)與外部設(shè)備之間通信的過程。
我們再先從應用程序的角度來解讀一下 I/O。
根據(jù)大學里學到的操作系統(tǒng)相關(guān)的知識:為了保證操作系統(tǒng)的穩(wěn)定性和安全性,一個進程的地址空間劃分為 用戶空間(User space) 和 內(nèi)核空間(Kernel space ) 。
像我們平常運行的應用程序都是運行在用戶空間,只有內(nèi)核空間才能進行系統(tǒng)態(tài)級別的資源有關(guān)的操作,比如如文件管理、進程通信、內(nèi)存管理等等。也就是說,我們想要進行 IO 操作,一定是要依賴內(nèi)核空間的能力。
并且,用戶空間的程序不能直接訪問內(nèi)核空間。
當想要執(zhí)行 IO 操作時,由于沒有執(zhí)行這些操作的權(quán)限,只能發(fā)起系統(tǒng)調(diào)用請求操作系統(tǒng)幫忙完成。
因此,用戶進程想要執(zhí)行 IO 操作的話,必須通過 系統(tǒng)調(diào)用 來間接訪問內(nèi)核空間
我們在平常開發(fā)過程中接觸最多的就是 磁盤 IO(讀寫文件)和 網(wǎng)絡(luò) IO(網(wǎng)絡(luò)請求和相應)。
從應用程序的視角來看的話,我們的應用程序?qū)Σ僮飨到y(tǒng)的內(nèi)核發(fā)起 IO 調(diào)用(系統(tǒng)調(diào)用),操作系統(tǒng)負責的內(nèi)核執(zhí)行具體的 IO 操作。也就是說,我們的應用程序?qū)嶋H上只是發(fā)起了 IO 操作的調(diào)用而已,具體 IO 的執(zhí)行是由操作系統(tǒng)的內(nèi)核來完成的。
當應用程序發(fā)起 I/O 調(diào)用后,會經(jīng)歷兩個步驟:
內(nèi)核等待 I/O 設(shè)備準備好數(shù)據(jù) 內(nèi)核將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間。
有哪些常見的 IO 模型?
UNIX 系統(tǒng)下, IO 模型一共有 5 種:同步阻塞 I/O、同步非阻塞 I/O、I/O 多路復用、信號驅(qū)動 I/O 和異步 I/O。
這也是我們經(jīng)常提到的 5 種 IO 模型。
Java 中 3 種常見 IO 模型
BIO (Blocking I/O)
BIO 屬于同步阻塞 IO 模型 。
同步阻塞 IO 模型中,應用程序發(fā)起 read 調(diào)用后,會一直阻塞,直到在內(nèi)核把數(shù)據(jù)拷貝到用戶空間。

在客戶端連接數(shù)量不高的情況下,是沒問題的。但是,當面對十萬甚至百萬級連接的時候,傳統(tǒng)的 BIO 模型是無能為力的。因此,我們需要一種更高效的 I/O 處理模型來應對更高的并發(fā)量。
NIO (Non-blocking/New I/O)
Java 中的 NIO 于 Java 1.4 中引入,對應 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解為 Non-blocking,不單純是 New。它支持面向緩沖的,基于通道的 I/O 操作方法。對于高負載、高并發(fā)的(網(wǎng)絡(luò))應用,應使用 NIO 。
Java 中的 NIO 可以看作是 I/O 多路復用模型。也有很多人認為,Java 中的 NIO 屬于同步非阻塞 IO 模型。
跟著我的思路往下看看,相信你會得到答案!
我們先來看看 同步非阻塞 IO 模型。

同步非阻塞 IO 模型中,應用程序會一直發(fā)起 read 調(diào)用,等待數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間的這段時間里,線程依然是阻塞的,直到在內(nèi)核把數(shù)據(jù)拷貝到用戶空間。
相比于同步阻塞 IO 模型,同步非阻塞 IO 模型確實有了很大改進。通過輪詢操作,避免了一直阻塞。
但是,這種 IO 模型同樣存在問題:應用程序不斷進行 I/O 系統(tǒng)調(diào)用輪詢數(shù)據(jù)是否已經(jīng)準備好的過程是十分消耗 CPU 資源的。
這個時候,I/O 多路復用模型 就上場了。

IO 多路復用模型中,線程首先發(fā)起 select 調(diào)用,詢問內(nèi)核數(shù)據(jù)是否準備就緒,等內(nèi)核把數(shù)據(jù)準備好了,用戶線程再發(fā)起 read 調(diào)用。read 調(diào)用的過程(數(shù)據(jù)從內(nèi)核空間->用戶空間)還是阻塞的。
目前支持 IO 多路復用的系統(tǒng)調(diào)用,有 select,epoll 等等。select 系統(tǒng)調(diào)用,是目前幾乎在所有的操作系統(tǒng)上都有支持
select 調(diào)用 :內(nèi)核提供的系統(tǒng)調(diào)用,它支持一次查詢多個系統(tǒng)調(diào)用的可用狀態(tài)。幾乎所有的操作系統(tǒng)都支持。 epoll 調(diào)用 :linux 2.6 內(nèi)核,屬于 select 調(diào)用的增強版本,優(yōu)化了 IO 的執(zhí)行效率。
IO 多路復用模型,通過減少無效的系統(tǒng)調(diào)用,減少了對 CPU 資源的消耗。
Java 中的 NIO ,有一個非常重要的選擇器 ( Selector ) 的概念,也可以被稱為 多路復用器。通過它,只需要一個線程便可以管理多個客戶端連接。當客戶端數(shù)據(jù)到了之后,才會為其服務(wù)。

AIO (Asynchronous I/O)
AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改進版 NIO 2,它是異步 IO 模型。
異步 IO 是基于事件和回調(diào)機制實現(xiàn)的,也就是應用操作之后會直接返回,不會堵塞在那里,當后臺處理完成,操作系統(tǒng)會通知相應的線程進行后續(xù)的操作。

目前來說 AIO 的應用還不是很廣泛。Netty 之前也嘗試使用過 AIO,不過又放棄了。這是因為,Netty 使用了 AIO 之后,在 Linux 系統(tǒng)上的性能并沒有多少提升。
最后,來一張圖,簡單總結(jié)一下 Java 中的 BIO、NIO、AIO。

參考
《深入拆解 Tomcat & Jetty》 如何完成一次 IO:https://llc687.top/post/如何完成一次-io/ 程序員應該這樣理解 IO:https://www.jianshu.com/p/fa7bdc4f3de7 10 分鐘看懂, Java NIO 底層原理:https://www.cnblogs.com/crazymakercircle/p/10225159.html IO 模型知多少 | 理論篇:https://www.cnblogs.com/sheng-jie/p/how-much-you-know-about-io-models.html 《UNIX 網(wǎng)絡(luò)編程 卷 1;套接字聯(lián)網(wǎng) API 》6.2 節(jié) IO 模型
