Java的BIO、NIO和AIO介紹
1. I/O概念理解:同步/非同步、阻塞/非阻塞
- 一個IO操作其實分成了兩個步驟:發起IO請求和實際的IO操作。
- 同步IO和非同步IO的區別就在於第二個步驟是否阻塞,如果實際的IO讀寫阻塞請求程序,那麼就是同步IO。
- 阻塞IO和非阻塞IO的區別在於第一步,發起IO請求是否會被阻塞,如果阻塞直到完成那麼就是傳統的阻塞IO,如果不阻塞,那麼就是非阻塞IO。
2.Java對BIO、NIO、AIO的支援
- Java BIO : 同步並阻塞,伺服器實現模式為一個連線一個執行緒,即客戶端有連線請求時伺服器端就需要啟動一個執行緒進行處理,如果這個連線不做任何事情會造成不必要的執行緒開銷,當然可以通過執行緒池機制改善。BIO方式適用於連線數目比較小且固定的架構,這種方式對伺服器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程式直觀簡單易理解。
- Java NIO : 同步非阻塞,伺服器實現模式為一個請求一個執行緒,即客戶端傳送的連線請求都會註冊到多路複用器上,多路複用器輪詢到連線有I/O請求時才啟動一個執行緒進行處理。
- Java AIO(NIO.2) : 非同步非阻塞,伺服器實現模式為一個有效請求一個執行緒,客戶端的I/O請求都是由OS先完成了再通知伺服器應用去啟動執行緒進行處理。AIO方式使用於連線數目多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫OS參與併發操作,程式設計比較複雜,JDK7開始支援。
3.高效能I/0
基本的I/O程式設計過程(包括網路I/O和檔案I/O)是,開啟檔案描述符(windows是handler,java是stream或channel),多路捕獲(Multiplexe,即select和poll和epoll)IO可讀寫的狀態,而後可以讀寫的檔案描述符進行IO讀寫,由於IO裝置速度和CPU記憶體比速度會慢,為了更好的利用CPU和記憶體,會開多執行緒,每個執行緒讀寫一個檔案描述符。
但C10K(Concurrent 10000 connection)問題,讓我們意識到在超大數量的網路連線下,機器裝置和網路速度不再是瓶頸,瓶頸在於作業系統和IO應用程式的溝通協作的方式。
舉個例子,一萬個socket連線過來,傳統的IO程式設計模型要開萬個執行緒來應對,還要注意,socket會關閉開啟,一萬個執行緒要不斷的關閉執行緒重建執行緒,資源都浪費在這上面了,我們算建立一個執行緒耗1M記憶體,1萬個執行緒機器至少要10G記憶體,這在IA-32的機器架構下基本是不可能的(要開PAE),現在x64架構才有可能舒服點,要知道,這僅僅是粗略算的記憶體消耗。別的資源呢?
所以,高效能的網路程式設計(即IO程式設計)。
- 第一,需要鬆綁IO連線和應用程式執行緒的對應關係,這就是非阻塞(nonblocking)、非同步(asynchronous)的要求的由來(構造一個執行緒池,epoll監控到有數的fd,把fd傳入執行緒池,由這些worker thread來讀寫io)。
- 第二,需要高效能的OS對IO裝置可讀寫(資料來了)的通知方式:從level-triggered notification到edge-triggered notification。
4.Linux的I/O選擇
Linux核心的AIO機制屢被吐槽,Java AIO或者雞肋?
Netty4的beta3加了AIO了,但是到beta9又被去了,作者的意思是測試下來AIO效能不如NIO,所以沒必要用。
AIO在Linux環境下的應用是否真的不如人意,其適用場景是什麼,這些本人還沒有深入研究和做實驗對比。而大量實踐證明,Netty框架能達到很高的I/O效能,能在linux讓應用和OS取得最高效率的溝通,它主要是基於NIO,Linux下使用的是epoll+edge-triggered notification。
參考資料: