Java I/O模型及其底層原理

  Java I/O是Java基礎之一,在面試中也比較常見,在這裡我們嘗試通過這篇文章闡述Java I/O的基礎概念,幫助大家更好的理解Java I/O。
  在剛開始學習Java I/O時,我很迷惑,因為網上絕大多數的文章都是講解Linux網路I/O模型的,那是我總是搞不明白和Java I/O的關係。後來查了看了好多,才明白Java I/O的原理是以Linux網路I/O模型為基礎的,理解了Linux網路I/O模型再學習Java I/O就很方便了,所以這篇文章,我們先來了解I/O的基本概念,再學習Linux網路I/O模型,最後再看Java中的幾種I/O。

什麼是I/O?

  I/O是Input、Output的縮寫,即對應電腦中的輸入輸出,以一次文件讀取為例,我們需要將磁碟上的數據讀取到用戶空間,那麼這次數據轉移操作其實就是一次I/O操作,更具體的說是一次文件I/O。我們瀏覽網頁,其中在請求一個網頁時,伺服器通過網路把數據發送給我們,此時程式將數據從TCP緩衝區複製到用戶空間,那麼這次數據轉移操作其實也是一次I/O操作,更具體的說是一次網路I/O。I/O到處都在,十分重要,Java對I/O對底層作業系統的各種I/O模型進行了封裝,使我們可以輕鬆開發。

Linux網路I/O模型

  根據UNIX網路編程對I/O模型的分類,UNIX提供了5種I/O模型,分別是:阻塞I/O(Blocking I/O)、非阻塞I/O(Non-Blacking I/O)、I/O多路復用模型(I/O Multiplexing)、訊號驅動式I/O(Signal Driven I/O)、非同步I/O(Asynchronous I/O)。我們逐步了解一下其基本原理。

阻塞I/O(Blocking I/O)

  阻塞I/O是最早最基礎的I/O模型,其在讀寫數據過程中會阻塞。通過下圖我們可以看到,當用戶進程調用了recvfrom這個系統調用後,內核開始第一階段的數據準備工作,直到內核等待數據準備完成,然後開始第二階段的將數據從內核複製到用戶空間的工作,最後內核返回結果。整個過程中用戶進程都是阻塞的,直到最後返回結果後才接觸阻塞block狀態。阻塞I/O模型適用於並發量小且對時延不敏感的系統。

非阻塞I/O(Non-Blacking I/O)

  當用戶進程調用recvfrom這個系統調用後,如果內核尚未準備好數據,此時不再阻塞用戶進程,而是立即返回一個EWOULDBLOCK錯誤。用戶進程會不斷發起系統調用直到內核中數據被準備好(輪詢),此時將執行第二階段的將數據從內核複製到用戶空間的工作,然後內核返回結果。非阻塞I/O模型不斷地輪詢往往需要耗費大量cpu時間。

I/O多路復用模型(I/O Multiplexing)

  I/O多路復用的優點在於單個進程可以同時處理多個網路連接的I/O,其基本原理就是select/epoll函數可以不斷的輪詢其負責的所有socket,當某個socket有數據到達時,就通知用戶進程。
  如下圖所示,當用戶進程調用select函數時,整個進程會被阻塞block住,但是這裡的阻塞不是被socket I/O阻塞,而是被select這個函數阻塞。同時內核會監聽改select負責的所有socket(這裡的socket一般設置為non-blocking),當任何一個socket中的數據準備好時,select就會返回給用戶進程,這時候用戶進程再此發起一個系統調用,將數據從內核複製到用戶空間,並返回結果。
  對比I/O多路復用模型和阻塞I/O模型的流程,多路復用多了一個系統調用來完成select環節,除此之外沒有太大的不同。Select的優勢在於它可以同時處理多個connection,但是會多一個系統調用。多路復用本質上也不是非阻塞的。

訊號驅動式I/O(Signal Driven I/O)

  首先我們開啟socket的訊號驅動I/O功能,然後用戶進程發起sigaction系統調用給內核後立即返回並可繼續處理其他工作。收到sigaction系統調用的內核在將數據準備好後會按照要求產生一個signo訊號通知給用戶進程。然後用戶進程再發起recvfrom系統調用,完成數據從內核到用戶空間的複製,並返回最終結果。其基礎原理圖示如下:

非同步I/O(Asynchronous I/O)

  用戶進程向內核發起系統調用後,就可以開始去做其他事情了。內核收到非同步I/O的系統調用後,會直接retrun,所以這裡不會對用戶進程有阻塞。之後內核等待數據準備完成後會繼續將數據從內核拷貝到用戶空間(具體動作可以由非同步I/O調用定義),然後內核回給用戶進程發送一個signal,告訴用戶進程I/O操作完成了,整個過程不會導致用戶請求進程阻塞。
  訊號驅動I/O模型是內核通知我們可以發起I/O操作了,而非同步I/O模式是內核告訴我們I/O操作已經完成了。

  以上就是Linux的5種網路I/O模型,其中前4中都是同步I/O模型,他們真正的I/O操作環節都會將進程阻塞,只有最後一種非同步I/O模型是非同步I/O操作。

Java中的I/O模型

  在JDK1.4之前,基於Java的所有socket通訊都是使用阻塞I/O(BIO),JDK1.4提供了了非阻塞I/O(NIO)功能,不過雖然名字叫做NIO,實際底層模型是I/O多路復用,JDK1.7提供了針對非同步I/O(AIO)功能。

BIO

  BIO簡化了上層開發,但是性能瓶頸問題嚴重,對高並發第時延支援差。
基於消息隊列和執行緒池技術優化的BIO模式雖然可以對高並發支援有一定幫助,但是還是受限於執行緒池大小和執行緒池阻塞隊列大小的制約,當並發數超過執行緒池的處理能力時,部分請求法務繼續處理,會導致客戶端連接超時,影響用戶體驗。

NIO

  NIO彌補了BIO的不足,簡單說就是通過selector不斷輪詢註冊在自己上面的channel,如果channel上面有新的連接讀寫時間時就會被輪詢出來,一個selector上面可以註冊多個channel,一個執行緒就可以負責selector的輪詢,這樣就可以支援成千上萬的連接。Selector就是一個輪詢器,channel是一個通道,通過它來讀取或者寫入數據,通道是雙向的,可以用於讀、寫、讀和寫。Buffer用來和channel交互,數據通過channel進出buffer。
NIO的優點是可以可靠性好以及高並發低時延,但是使用NIO的程式碼開發較為複雜。

AIO

  AIO,或者說叫做NIO2.0,引入了非同步channel的概念,提供了非同步文件channel和非同步socket channel的實現,開發者可以通過Future類來表示非同步操作的結果,也可以在執行非同步操作時傳入一個channels,實現CompletionHandler介面作為回調。AIO不用開發者單獨開發獨立執行緒的selector,非同步回調操作有JDK地城思安城池負責驅動,開發起來比NIO簡單一些,同時保持了高可靠高並發低時延的優點。

參考:
//blog.csdn.net/historyasamirror/article/details/5778378
//juejin.im/post/5cce5019e51d453a506b0ebf

Tags: