如何理解BIO、NIO、AIO的區別?
很多文章在談論到BIO、NIO、AIO的時候僅僅是丟擲一堆定義,以及一些生動的例子。看似很好理解。但是並沒有將最基礎的本質原理顯現出來,如果沒有沒有從IO的原理出發的話是很難理解這三者之間的區別的。所以本篇文章從Java是如何進行IO操作為開頭進行分析。
Java中的IO原理
首先Java中的IO都是依賴作業系統核心進行的,我們程式中的IO讀寫其實呼叫的是作業系統核心中的read&write兩大系統呼叫。
那核心是如何進行IO互動的呢?
- 網路卡收到經過網線傳來的網路資料,並將網路資料寫到記憶體中。
- 當網路卡把資料寫入到記憶體後,網路卡向cpu發出一箇中斷訊號,作業系統便能得知有新資料到來,再通過網路卡中斷程式去處理資料。
- 將記憶體中的網路資料寫入到對應socket的接收緩衝區中。
- 當接收緩衝區的資料寫好之後,應用程式開始進行資料處理。
對應抽象到java的socket程式碼簡單示例如下:
public class SocketServer {
public static void main(String[] args) throws Exception {
// 監聽指定的埠
int port = 8080;
ServerSocket server = new ServerSocket(port);
// server將一直等待連線的到來
Socket socket = server.accept();
// 建立好連線後,從socket中獲取輸入流,並建立緩衝區進行讀取
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes)) != -1) {
//獲取資料進行處理
String message = new String(bytes,len,"UTF-8");
}
// socket、server,流關閉操作,省略不表
}
}
複製程式碼
可以看到這個過程和底層核心的網路IO很類似,主要體現在accept()等待從網路中的請求到來然後bytes[]陣列作為緩衝區等待資料填滿後進行處理。而BIO、NIO、AIO之間的區別就在於這些操作是同步還是非同步,阻塞還是非阻塞。
所以我們引出同步非同步,阻塞與非阻塞的概念。
同步與非同步
同步和非同步指的是一個執行流程中每個方法是否必須依賴前一個方法完成後才可以繼續執行。假設我們的執行流程中:依次是方法一和方法二。
同步指的是呼叫一旦開始,呼叫者必須等到方法呼叫返回後,才能繼續後續的行為。即方法二一定要等到方法一執行完成後才可以執行。
非同步指的是呼叫立刻返回,呼叫者不必等待方法內的程式碼執行結束,就可以繼續後續的行為。(具體方法內的程式碼交由另外的執行緒執行完成後,可能會進行回撥)。即執行方法一的時候,直接交給其他執行緒執行,不由主執行緒執行,也就不會阻塞主執行緒,所以方法二不必等到方法一完成即可開始執行。
同步與非同步關注的是方法的執行方是主執行緒還是其他執行緒,主執行緒的話需要等待方法執行完成,其他執行緒的話無需等待立刻返回方法呼叫,主執行緒可以直接執行接下來的程式碼。
同步與非同步是從多個執行緒之間的協調來實現效率差異。
為什麼需要非同步呢?筆者認為非同步的本質就是為瞭解決主執行緒的阻塞,所以網上很多討論把同步非同步、阻塞非阻塞進行了四種組合,其中一種就有非同步阻塞這一情形,如果非同步也是阻塞的?那為什麼要特地進行非同步操作呢?
阻塞與非阻塞
阻塞與非阻塞指的是單個執行緒內遇到同步等待時,是否在原地不做任何操作。
阻塞指的是遇到同步等待後,一直在原地等待同步方法處理完成。
非阻塞指的是遇到同步等待,不在原地等待,先去做其他的操作,隔斷時間再來觀察同步方法是否完成。
阻塞與非阻塞關注的是執行緒是否在原地等待。
筆者認為阻塞和非阻塞僅能與同步進行組合。而非同步天然就是非阻塞的,而這個非阻塞是對主執行緒而言。(可能有人認為非同步方法裡面放入阻塞操作的話就是非同步阻塞,但是思考一下,正是因為是阻塞操作所以才會將它放入非同步方法中,不要阻塞主執行緒)
例子講解
海底撈很好吃,但是經常要排隊。我們就以生活中的這個例子進行講解。
- A顧客去吃海底撈,就這樣乾坐著等了一小時,然後才開始吃火鍋。(BIO)
- B顧客去吃海底撈,他一看要等挺久,於是去逛商場,每次逛一會就跑回來看有沒有排到他。於是他最後既購了物,又吃上海底撈了。(NIO)
- C顧客去吃海底撈,由於他是高階會員,所以店長說,你去商場隨便玩吧,等下有位置,我立馬打電話給你。於是C顧客不用幹坐著等,也不用每過一會兒就跑回來看有沒有等到,最後也吃上了海底撈(AIO)
哪種方式更有效率呢?是不是一目瞭然呢?
BIO
BIO全稱是Blocking IO,是JDK1.4之前的傳統IO模型,本身是同步阻塞模式。 執行緒發起IO請求後,一直阻塞IO,直到緩衝區資料就緒後,再進入下一步操作。針對網路通訊都是一請求一應答的方式,雖然簡化了上層的應用開發,但在效能和可靠性方面存在著巨大瓶頸,試想一下如果每個請求都需要新建一個執行緒來專門處理,那麼在高併發的場景下,機器資源很快就會被耗盡。
NIO
NIO也叫Non-Blocking IO 是同步非阻塞的IO模型。執行緒發起io請求後,立即返回(非阻塞io)。同步指的是必須等待IO緩衝區內的資料就緒,而非阻塞指的是,使用者執行緒不原地等待IO緩衝區,可以先做一些其他操作,但是要定時輪詢檢查IO緩衝區資料是否就緒。Java中的NIO 是new IO的意思。其實是NIO加上IO多路複用技術。普通的NIO是執行緒輪詢檢視一個IO緩衝區是否就緒,而Java中的new IO指的是執行緒輪詢地去檢視一堆IO緩衝區中哪些就緒,這是一種IO多路複用的思想。IO多路複用模型中,將檢查IO資料是否就緒的任務,交給系統級別的select或epoll模型,由系統進行監控,減輕使用者執行緒負擔。
NIO主要有buffer、channel、selector三種技術的整合,通過零拷貝的buffer取得資料,每一個客戶端通過channel在selector(多路複用器)上進行註冊。服務端不斷輪詢channel來獲取客戶端的資訊。channel上有connect,accept(阻塞)、read(可讀)、write(可寫)四種狀態標識。根據標識來進行後續操作。所以一個服務端可接收無限多的channel。不需要新開一個執行緒。大大提升了效能。
AIO
AIO是真正意義上的非同步非阻塞IO模型。 上述NIO實現中,需要使用者執行緒定時輪詢,去檢查IO緩衝區資料是否就緒,佔用應用程式執行緒資源,其實輪詢相當於還是阻塞的,並非真正解放當前執行緒,因為它還是需要去查詢哪些IO就緒。而真正的理想的非同步非阻塞IO應該讓核心系統完成,使用者執行緒只需要告訴核心,當緩衝區就緒後,通知我或者執行我交給你的回撥函式。
AIO可以做到真正的非同步的操作,但實現起來比較複雜,支援純非同步IO的作業系統非常少,目前也就windows是IOCP技術實現了,而在Linux上,底層還是是使用的epoll實現的。