天天看點

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。

申請阿裡雲服務時,可以使用

2000元阿裡雲代金券

,阿裡雲官網領取網址:

https://dashi.aliyun.com/site/yun/youhui

什麼是I/O?

I/O是Input、Output的縮寫,即對應計算機中的輸入輸出,以一次檔案讀取為例,我們需要将磁盤上的資料讀取到使用者空間,那麼這次資料轉移操作其實就是一次I/O操作,更具體的說是一次檔案I/O。我們浏覽網頁,其中在請求一個網頁時,伺服器通過網絡把資料發送給我們,此時程式将資料從TCP緩沖區複制到使用者空間,那麼這次資料轉移操作其實也是一次I/O操作,更具體的說是一次網絡I/O。I/O到處都在,十分重要,Java對I/O對底層作業系統的各種I/O模型進行了封裝,使我們可以輕松開發。

阿裡雲伺服器1核2G低至82元/年

,阿裡雲官活動網址:

https://dashi.aliyun.com/site/yun/aliyun

可以用20代金券,即102-20=82。

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簡單一些,同時保持了高可靠高并發低延遲時間的優點。