京東數科二面:常見的 IO 模型有哪些?Java 中的 BIO、NIO、AIO 有啥區別?
阿新 • • 發佈:2021-02-25
IO 模型這塊確實挺難理解的,需要太多計算機底層知識。寫這篇文章用了挺久,就非常希望能把我所知道的講出來吧!希望朋友們能有收貨!為了寫這篇文章,還翻看了一下《UNIX 網路程式設計》這本書,太難了,我滴乖乖!心痛~
_個人能力有限。如果文章有任何需要補充/完善/修改的地方,歡迎在評論區指出,共同進步!_
## 前言
I/O 一直是很多小夥伴難以理解的一個知識點,這篇文章我會將我所理解的 I/O 講給你聽,希望可以對你有所幫助。
## I/O
### 何為 I/O?
I/O(**I**nput/**O**utpu) 即**輸入/輸出** 。
**我們先從計算機結構的角度來解讀一下 I/O。**
根據馮.諾依曼結構,計算機結構分為 5 大部分:運算器、控制器、儲存器、輸入裝置、輸出裝置。
![馮諾依曼體系結構](https://img-blog.csdnimg.cn/20190624122126398.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9pcy1jbG91ZC5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
輸入裝置(比如鍵盤)和輸出裝置(比如滑鼠)都屬於外部裝置。網絡卡、硬碟這種既可以屬於輸入裝置,也可以屬於輸出裝置。
輸入裝置向計算機輸入資料,輸出裝置接收計算機輸出的資料。
**從計算機結構的視角來看的話, I/O 描述了計算機系統與外部裝置之間通訊的過程。**
**我們再先從應用程式的角度來解讀一下 I/O。**
根據大學裡學到的作業系統相關的知識:為了保證作業系統的穩定性和安全性,一個程序的地址空間劃分為 **使用者空間(User space)** 和 **核心空間(Kernel space )** 。
像我們平常執行的應用程式都是執行在使用者空間,只有核心空間才能進行系統態級別的資源有關的操作,比如如檔案管理、程序通訊、記憶體管理等等。也就是說,我們想要進行 IO 操作,一定是要依賴核心空間的能力。
並且,使用者空間的程式不能直接訪問核心空間。
當想要執行 IO 操作時,由於沒有執行這些操作的許可權,只能發起系統呼叫請求作業系統幫忙完成。
因此,使用者程序想要執行 IO 操作的話,必須通過 **系統呼叫** 來間接訪問核心空間
我們在平常開發過程中接觸最多的就是 **磁碟 IO(讀寫檔案)** 和 **網路 IO(網路請求和相應)**。
**從應用程式的視角來看的話,我們的應用程式對作業系統的核心發起 IO 呼叫(系統呼叫),作業系統負責的核心執行具體的 IO 操作。也就是說,我們的應用程式實際上只是發起了 IO 操作的呼叫而已,具體 IO 的執行是由作業系統的核心來完成的。**
當應用程式發起 I/O 呼叫後,會經歷兩個步驟:
1. 核心等待 I/O 裝置準備好資料
2. 核心將資料從核心空間拷貝到使用者空間。
### 有哪些常見的 IO 模型?
UNIX 系統下, IO 模型一共有 5 種: **同步阻塞 I/O**、**同步非阻塞 I/O**、**I/O 多路複用**、**訊號驅動 I/O** 和**非同步 I/O**。
這也是我們經常提到的 5 種 IO 模型。
## Java 中 3 種常見 IO 模型
### BIO (Blocking I/O)
**BIO 屬於同步阻塞 IO 模型** 。
同步阻塞 IO 模型中,應用程式發起 read 呼叫後,會一直阻塞,直到在核心把資料拷貝到使用者空間。
![圖源:《深入拆解Tomcat & Jetty》](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6a9e704af49b4380bb686f0c96d33b81~tplv-k3u1fbpfcp-watermark.image)
在客戶端連線數量不高的情況下,是沒問題的。但是,當面對十萬甚至百萬級連線的時候,傳統的 BIO 模型是無能為力的。因此,我們需要一種更高效的 I/O 處理模型來應對更高的併發量。
### NIO (Non-blocking/New I/O)
Java 中的 NIO 於 Java 1.4 中引入,對應 `java.nio` 包,提供了 `Channel` , `Selector`,`Buffer` 等抽象。NIO 中的 N 可以理解為 Non-blocking,不單純是 New。它支援面向緩衝的,基於通道的 I/O 操作方法。 對於高負載、高併發的(網路)應用,應使用 NIO 。
Java 中的 NIO 可以看作是 **I/O 多路複用模型**。也有很多人認為,Java 中的 NIO 屬於同步非阻塞 IO 模型。
跟著我的思路往下看看,相信你會得到答案!
我們先來看看 **同步非阻塞 IO 模型**。
![圖源:《深入拆解Tomcat & Jetty》](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bb174e22dbe04bb79fe3fc126aed0c61~tplv-k3u1fbpfcp-watermark.image)
同步非阻塞 IO 模型中,應用程式會一直髮起 read 呼叫,等待資料從核心空間拷貝到使用者空間的這段時間裡,執行緒依然是阻塞的,直到在核心把資料拷貝到使用者空間。
相比於同步阻塞 IO 模型,同步非阻塞 IO 模型確實有了很大改進。通過輪詢操作,避免了一直阻塞。
但是,這種 IO 模型同樣存在問題:**應用程式不斷進行 I/O 系統呼叫輪詢資料是否已經準備好的過程是十分消耗 CPU 資源的。**
這個時候,**I/O 多路複用模型** 就上場了。
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/88ff862764024c3b8567367df11df6ab~tplv-k3u1fbpfcp-watermark.image)
IO 多路複用模型中,執行緒首先發起 select 呼叫,詢問核心資料是否準備就緒,等核心把資料準備好了,使用者執行緒再發起 read 呼叫。read 呼叫的過程(資料從核心空間->使用者空間)還是阻塞的。
> 目前支援 IO 多路複用的系統呼叫,有 select,epoll 等等。select 系統呼叫,是目前幾乎在所有的作業系統上都有支援
>
> - **select 呼叫** :核心提供的系統呼叫,它支援一次查詢多個系統呼叫的可用狀態。幾乎所有的作業系統都支援。
> - **epoll 呼叫** :linux 2.6 核心,屬於 select 呼叫的增強版本,優化了 IO 的執行效率。
**IO 多路複用模型,通過減少無效的系統呼叫,減少了對 CPU 資源的消耗。**
Java 中的 NIO ,有一個非常重要的**選擇器 ( Selector )** 的概念,也可以被稱為 **多路複用器**。通過它,只需要一個執行緒便可以管理多個客戶端連線。當客戶端資料到了之後,才會為其服務。
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0f483f2437ce4ecdb180134270a00144~tplv-k3u1fbpfcp-watermark.image)
### AIO (Asynchronous I/O)
AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改進版 NIO 2,它是非同步 IO 模型。
非同步 IO 是基於事件和回撥機制實現的,也就是應用操作之後會直接返回,不會堵塞在那裡,當後臺處理完成,作業系統會通知相應的執行緒進行後續的操作。
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3077e72a1af049559e81d18205b56fd7~tplv-k3u1fbpfcp-watermark.image)
目前來說 AIO 的應用還不是很廣泛。Netty 之前也嘗試使用過 AIO,不過又放棄了。這是因為,Netty 使用了 AIO 之後,在 Linux 系統上的效能並沒有多少提升。
最後,來一張圖,簡單總結一下 Java 中的 BIO、NIO、AIO。
![](https://images.xiaozhuanlan.com/photo/2020/33b193457c928ae02217480f994814b6.png)
## 參考
- 《深入拆解 Tomcat & Jetty》
- 如何完成一次 IO:[https://llc687.top/post/如何完成一次-io/](https://llc687.top/post/如何完成一次-io/)
- 程式設計師應該這樣理解 IO:[https://www.jianshu.com/p/fa7bdc4f3de7](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 網路程式設計 卷 1;套接字聯網 API 》6.2 節 I