在Java中,有三種IO模型: BIO,NIO,AIO。介紹這三種IO模型以前,須要介紹一下同步,異步與阻塞,非阻塞的概念,而後再從Java和Linux OS的角度去分析BIO,NIO和AIOjava
同步linux
同步就是發起一個調用後,被調用者未處理完請求以前,調用不返回。編程
通俗的例子描述同步就像:緩存
你打電話問書店老闆有沒有《葵花寶典》這本書的時候,若是是同步機制,書店老闆會說,你稍等,」我查一下",而後開始查啊查,等查好了(多是5秒,也多是一天)告訴你結果(返回結果)。服務器
異步網絡
異步就是發起一個調用後,馬上獲得被調用者的迴應表示已接收到請求,可是被調用者並無返回結果,此時咱們能夠處理其餘的請求,被調用者一般依靠事件,回調等機制來通知調用者其返回結果。框架
通俗的例子描述異步就像:異步
而異步機制,書店老闆直接告訴你我查一下啊,查好了打電話給你,而後直接掛電話了(不返回結果)。而後查好了,他會主動打電話給你。在這裏老闆經過「回電」這種方式來回調函數
此處參照知乎上關於此問題的回答:www.zhihu.com/question/19…post
再次總結一下同步與異步:
同步與異步最大的區別就是被調用方的執行方式和返回時機,同步指的是被調用方作完事情以後再返回,異步指的是被調用方先返回,而後再作事情,作完以後再想辦法通知調用方
阻塞
阻塞就是發起一個請求,調用者一直等待請求結果返回,也就是當前線程會被掛起,沒法從事其餘任務,只有當條件就緒才能繼續。
非阻塞
非阻塞就是發起一個請求,調用者不用一直等着結果返回,能夠先去幹其餘事情。
仍是上面買書的例子:
你打電話問書店老闆有沒有《葵花寶典》這本書,你若是是阻塞式調用,你會一直把本身「掛起」,直到獲得這本書有沒有的結果,若是是非阻塞式調用,你無論老闆有沒有告訴你,你本身先一邊去玩了, 固然你也要偶爾過幾分鐘check一下老闆有沒有返回結果。
阻塞和同步不是一回事,同步,異步與阻塞,非阻塞針對的對象是不同的,阻塞,非阻塞是說的調用者,同步,異步說的是被調用者
BIO(Blocking I/O):BIO也就是傳統的同步阻塞IO模型,對應Java.io包,它提供了不少IO功能,好比輸入輸出流,對文件進行操做。在網絡編程(Socket通訊)中也一樣進行IO操做。
NIO(New I/O): NIO是一種同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,對應 java.nio 包,提供了 Channel , Selector,Buffer等抽象
AIO: AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是異步非阻塞的IO模型
上面簡單介紹了Java中的三種IO模型,三種模型提供的與IO有關的API,在文件處理時,底層其實是依賴操做系統層面的IO操做實現的,好比在Linux 2.6之後,Java中的NIO和AIO都是經過 epoll來實現的,關於epoll等概念後面也會闡述。
而實際上在Linux(Unix)操做系統中,共有五種 IO模型,分別是:阻塞IO模型、非阻塞IO模型、IO複用模型、信號驅動IO模型以及異步IO模型,而4種都是同步的,只有最後一種是異步的。下面的分析主要參考了《UNIX網絡編程 卷1:套接字聯網API(第3版)中的介紹。
一個輸入操做一般包括兩個不一樣的階段:
對於一個套接字上的輸入操做,第一步一般涉及等待數據從網絡中到達,當所等待分組到達時,它被複制到內核中的某個緩衝區,第二步就是把數據從內核緩衝區複製到應用進程緩衝區。
從上圖能夠看出,應用進程經過 系統調用 recvfrom
去接收數據,而因爲內核數據沒有準備好,應用進程就會阻塞,直到內核準備好數據並將其從內核複製到應用進程的緩衝區中或者發生錯誤才返回。最多見的錯誤就是系統調用被信號中斷。進程從調用recvfrom開始到它返回的整段時間內是被阻塞的。
Linux下的阻塞式I/O模型就對應了Java下的BIO模型,BIO的底層實現是調用操做系統的API去執行的,也就是調用操做系統的Socket套接字。
應用進程經過系統調用 recvfrom
不斷的去和內核交互,直到內核數據報準備好,而若是內核無數據準備好,轉而當即返回一個 EWOULDBLOCK
的錯誤,過一段時間再次發送 recvfrom
請求,在此期間進程能夠作其餘事情,不用一直等待,這就是非阻塞。
當一個應用進程循環調用 recvfrom
時,咱們稱之爲輪詢(polling),應用進程持續輪詢內核,以查看某個操做是否就緒。Java的NIO映射到Linux操做系統就是如上圖所示的非阻塞I/O模型
IO多路複用使用select/poll/epoll
函數,多個進程的IO均可以註冊在同一個 select
上,當用戶進程調用該 select
時,select
去監聽全部註冊好的IO,若是全部被監聽的IO須要的數據都沒有準備好,那麼 select
調用進程會被阻塞,只要任意一個IO的數據報套接字變爲可讀,即數據報已經準備好,select
就返回套接字可讀這一條件,而後調用 recvfrom
把所讀數據報復制到應用進程緩衝區。
強調一點就是,IO多路複用模型並無涉及到非阻塞,進程在發出select
後,要一直阻塞等待其監聽的全部IO操做至少有一個數據準備好才返回,強調阻塞狀態,不存在非阻塞。
而在 Java NIO中也能夠實現多路複用,主要是利用多路複用器 Selector,與這裏的 select
函數類型,Selector會不斷輪詢註冊在其上的通道Channel,若是有某一個Channel上面發生讀或寫事件,這個Channel處於就緒狀態,就會被Selector輪詢出來。關於Java NIO實現多路複用更多的介紹請查詢相關文章。
I/O 多路複用的主要應用場景以下:
目前支持I/O 多路複用的系統調用函數有 select,pselect,poll,epoll
。在Linux 網絡編程中,很長一段時間都使用select
作輪詢和網絡事件通知。然而由於select的一些固有缺陷致使它的應用受到了很大的限制,好比select 單個進程打開的最大句柄數是有限的。最終在 Linux 2.6 選擇epoll 替代了select,Java NIO和AIO底層就是用epoll。更多關於這些系統調用的介紹與使用,請參閱 《UNIX網絡編程 卷1:套接字聯網API(第3版)
應用進程預先向內核安裝一個信號處理函數,而後當即返回,進程繼續工做,不阻塞,當數據報準備好讀取時,內核就爲該進程產生一個信號通知進程,而後進程再調用recvfrom讀取數據報。
信號驅動式IO在數據準備階段是異步的,當內核中有數據報準備後再通知進程,可是在調用 recvfrom操做進行數據拷貝時是同步的,因此整體來講,整個IO過程不能是異步的。
應用進程調用aio_read
函數,給內核傳遞描述符,緩存區指針,緩存區大小和文件偏移,並告訴內核當整個操做完成時如何通知進程,而後該系統調用當即返回,並且在等待I/O完成期間,咱們的進程不被阻塞,進程能夠去幹其餘事情,而後內核開始等待數據準備,數據準備好之後再拷貝數據到進程緩衝區,最後通知整個IO操做已完成。
Java的AIO提供了異步通道API,其操做系統底層實現就是這個異步I/O模型
主要區別在於: 信號驅動式I/O是由內核通知咱們什麼時候去啓動一個I/O操做,而異步I/O模型是由內核通知咱們I/O操做什麼時候完成。
由上圖能夠再次看出,IO操做主要分爲兩個階段:
前4種IO模型都是同步IO模型,爲何說都是同步的,由於它們在第二步數據拷貝階段都是阻塞的,這會致使整個請求進程存在阻塞的狀況,因此是同步的,而異步IO模型不會致使請求進程阻塞。
上面簡要闡述了BIO,NIO,AIO以及Linux下的5種IO模型,對於IO模型更加詳細的介紹參考《UNIX網絡編程 卷1:套接字聯網API(第3版)