BIO/NIO/AIO(總結版)

1.前言

相信大家在網上看過不少講解 BIO/NIO/AIO 的文章,文章中舉起栗子來更是夯喫夯喫一大堆,我是越看越覺得 What are you 你講啥嘞?

本文將針對 BIO/NIO/AIO 、阻塞與非阻塞、同步與異步等特別容易混淆的概念進行對比區分,理清混亂的思路。

2.魔幻的IO模型

BIO (同步阻塞I/O)

數據的讀取寫入必須阻塞在一個線程內等待其完成。

這裏使用那個經典的燒開水例子,這裏假設一個燒開水的場景,有一排水壺在燒開水,BIO的工作模式就是, 叫一個線程停留在一個水壺那,直到這個水壺燒開,纔去處理下一個水壺。但是實際上線程在等待水壺燒開的時間段什麼都沒有做。

NIO(同步非阻塞)

同時支持阻塞與非阻塞模式,但這裏我們以其同步非阻塞I/O模式來說明,那麼什麼叫做同步非阻塞?如果還拿燒開水來說,NIO的做法是叫一個線程不斷的輪詢每個水壺的狀態,看看是否有水壺的狀態發生了改變,從而進行下一步的操作。

AIO (異步非阻塞I/O)

異步非阻塞與同步非阻塞的區別在哪裏?異步非阻塞無需一個線程去輪詢所有IO操作的狀態改變,在相應的狀態改變後,系統會通知對應的線程來處理。對應到燒開水中就是,爲每個水壺上面裝了一個開關,水燒開之後,水壺會自動通知我水燒開了。

上面這些燒開水(或者服務員端菜)的例子百度一下相當多,但只能幫你理解些相關概念,使你知其然但不知其所以然,下面我會對概念進一步加深理解,並加以區分。

3.同步與異步的區別

同步和異步是針對應用程序和內核的交互而言的,同步指的是用戶進程觸發IO操作並等待或者輪詢的去查看IO操作是否就緒,而異步是指用戶進程觸發IO操作以後便開始做自己的事情,而當IO操作已經完成的時候會得到IO完成的通知。

簡而言之,同步和異步最關鍵的區別在於同步必須等待(BIO)或者主動的去詢問(NIO)IO是否完成,而異步(AIO)操作提交後只需等待操作系統的通知即可。(思考一下:操作系統底層通過什麼去通知數據使用者?)

大型網站一般都會使用消息中間件進行解藕、異步、削峯,生產者將消息發送給消息中間件就返回,消息中間件將消息轉發到消費者進行消費,這種操作方式其實就是異步。

與之相比,什麼是同步?

生產者將消息發送到消息中間件,消息中間件將消息發送給消費者,消息者消費後返回響應給消息中間件,消息中間件返回響應給生產者,該過程由始至終都需要生產者進行參與,這就是同步操作。

(注:上面的舉例只用於理解BIO/NIO概念,不代表消息中間件的真實使用過程)

4.阻塞和非阻塞的區別

阻塞和非阻塞是針對於進程在訪問數據的時候,根據IO操作的就緒狀態來採取的不同方式,說白了是一種讀取或者寫入操作方法的實現方式,阻塞方式下讀取或者寫入函數將一直等待(BIO),而非阻塞方式下,讀取或者寫入方法會立即返回一個狀態值(NIO)

BIO對應的Socket網絡編程代碼如下,其中server.accept()代碼會一直阻塞當前線程,直到有新的客戶端與之連接後,就創建一個新的線程進行處理,注意這裏是一次連接創建一個線程。

public static void main(String[] args) throws IOException {
        int port = 8899;
        // 定義一個ServiceSocket監聽在端口8899上
        ServerSocket server = new ServerSocket(port);
        System.out.println("等待與客戶端建立連接...");
        while (true) {
            // server嘗試接收其他Socket的連接請求,server的accept方法是阻塞式的
            Socket socket = server.accept();
            // 每接收到一個Socket就建立一個新的線程來處理它
            new Thread(new Task(socket)).start();
        }
        // server.close();
}

NIO的Socket網絡編程代碼如下圖(在網上找了半天),我們只需要觀察NIO的關鍵兩個點:輪詢、IO多路複用。

找到while(true){}代碼就找到了輪詢的代碼,其中調用的 selector.select() 方法會一直阻塞到某個註冊的通道有事件就緒,然後返回當前就緒的通道數,也就是非阻塞概念中提到的狀態值。

5.IO多路複用

我們都聽說過NIO具有IO多路複用,其實關鍵點就在於NIO創建一個連接後,是不需要創建對應的一個線程,這個連接會被註冊到多路複用器(Selector)上面,所以所有的連接只需要一個線程就可以進行管理,當這個線程中的多路複用器進行輪詢的時候,發現連接上有請求數據的話,纔開啓一個線程進行處理,也就是一個有效請求一個線程模式。如果連接沒有數據,是沒有工作線程來處理的。

光講概念恐怕讀者很難聽的懂,所以我還是以上面那張圖中的代碼講解。

在代碼中,main方法所在的主線程擁有多路複用器並開啓了一個主機端口進行通信,所有的客戶端連接都會被註冊到主線程所在的多路複用器,通過輪詢while(true){}不斷檢測多路複用器上所有連接的狀態,也就是 selectedKey 提供的API。發現請求有效,就開啓一個線程進行處理,無效的請求,就不需要創建線程進行處理。

與BIO對比不難發現,這種方式相比BIO一次連接創建一個線程大大減少了線程的創建數量,性能豈能不提高。

6.AIO:異步非阻塞的編程方式

BIO/NIO都需要在調用讀寫方法後,要麼一直等待,要麼輪詢查看,直到有了結果再來執行後續代碼,這就是同步操作了。

而AIO則是真正的異步,當進行讀寫操作時,只須直接調用API的 read 或 write 方法即可。對於讀操作而言,當有流可讀取時,操作系統會將可讀的流傳入 read 方法的緩衝區,並通知應用程序;對於寫操作而言,當操作系統將 write 方法傳遞的流寫入完畢時,操作系統主動通知應用程序。你可以理解爲,read/write 方法都是異步的,完成後會主動調用回調函數,這也就是同步與異步真正的區別了。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章