Java的BIO,NIO和AIO的區別於演進

做者:公衆號:我是攻城師 

 

 

前言

Java裏面的IO模型種類較多,主要包括BIO,NIO和AIO,每一個IO模型都有不同的地方,那麼這些IO模型是如何演變呢,底層的原理又是怎樣的呢? 本文咱們就來聊聊。java

BIO

BIO全稱是Blocking IO,是JDK1.4以前的傳統IO模型,自己是同步阻塞模式,針對網絡通訊都是一請求一應答的方式,雖然簡化了上層的應用開發,但在性能和可靠性方面存在着巨大瓶頸,試想一下若是每一個請求都須要新建一個線程來專門處理,那麼在高併發的場景下,機器資源很快就會被耗盡,固然,咱們能夠經過線程池來優化這種狀況,但即便是這樣,仍然改變不了阻塞IO的根本問題,就是在IO執行的兩個階段都被block了。拿一個read操做來舉例子,在linux中,應用程序向linux發起read操做,會經歷兩個步驟:linux

第一個階段linux內核首先會把須要讀取的數據加載到操做系統內核的緩衝區中(Linux文件系統是緩存IO,也稱標準IO)編程

第二個階段應用程序拷貝內核裏面的數據到本身的用戶空間中windows

若是是socket操做,相似也會經歷兩個步驟:api

第一個階段:一般涉及等待網絡上的數據分組包到達,而後被複制到內核的緩衝區緩存

第二個階段:把數據從內核緩衝區,從內核緩衝區拷貝到用戶進程的內存空間裏面網絡

同步阻塞IO之因此效率低下,就是由於在這兩個階段,用戶的線程或者進程都是阻塞的,期間雖然不佔cpu資源,但也意味着該線程也不能再幹其餘事。有點站着茅坑不拉屎的感受,本身暫時不用了,也不讓別人用。多線程

圖示以下:併發

 

NIO

因爲BIO的缺點,致使Java在JDK1.0至JDK3.0中,網絡通訊模塊的性能一直是短板,因此不少人更傾向於使用C/C++開發高性能服務端。爲了強化Java在服務端的市場,終於在JSR-51也就是JDK4.0的時候發佈了Java NIO,能夠支持非阻塞IO。並新增了java.nio的包,提供不少異步開發的API和類庫。框架

主要的類和接口以下:

(1)進行異步IO操做的緩衝區ByteBuffer

(2)進行異步IO操做的管道Pipe

(3)進行各類IO操做的Channel,主要包括ServerSocketChannel和SocketChannel

(4)實現非阻塞IO的多路複用器Selector

NIO主要有buffer、channel、selector三種技術的整合,經過零拷貝的buffer取得數據,每個客戶端經過channel在selector(多路複用器)上進行註冊。服務端不斷輪詢channel來獲取客戶端的信息。channel上有connect,accept(阻塞)、read(可讀)、write(可寫)四種狀態標識。根據標識來進行後續操做。因此一個服務端可接收無限多的channel。不須要新開一個線程。大大提高了性能。

新的nio類庫,促進了異步非阻塞編程的發展和應用,但仍然有一些不足之處:

(1)沒有統一的文件屬性,例如讀寫權限

(2)api能力比較弱,例如目錄的及聯建立和遞歸遍歷,每每須要本身完成。

(3)底層操做系統的一些高級API沒法使用

(4)全部的文件操做都是同步阻塞調用,在操做系統層面上並非異步文件讀寫操做。

Java裏面的NIO其實採用了多路複用的IO模式,多路複用的模式在Linux底層實際上是採用了select,poll,epoll的機制,這種機制能夠用單個線程同時監聽多個io端口,當其中任何一個socket的數據準備好了,就能返回通知用戶線程進行讀取操做,與阻塞IO阻塞的是每個用戶的線程不同的地方是,多路複用只須要阻塞一個用戶線程便可,這個用戶線程一般咱們叫它Selector,其實底層調用的是內核的select,這裏面只要任何一個IO操做就緒,就能夠喚醒select,而後交由用戶線程處理。用戶線程讀取數據這個過程仍然是阻塞的,多路複用技術只是在第一個階段能夠變爲非阻塞調用,但在第二個階段拷貝數據到用戶空間,其實仍是阻塞的,多路複用技術的最大特色是使用一個線程就能夠處理不少的socket鏈接,儘管性能上不必定提高,但支持併發能力卻大大加強了。

圖示以下:

 

AIO

AIO,實際上是NIO的改進優化,也被稱爲NIO2.0,在2011年7月,也就是JDK7的版本中發佈,它主要提供了三個方面的改進:

(1)提供了可以批量獲取文件屬性的api,經過SPI服務,使得這些API具備平臺無關性。

(2)提供了AIO的功能,支持基於文件的異步IO操做和網絡套接字的異步操做

(3)完成了JSR-51定義的通道功能等。

AIO 經過調用accept方法,一個會話接入以後再次調用(遞歸)accept方法,監聽下一次會話,讀取也再也不阻塞,回調complete方法異步進行。再也不須要selector 使用channel線程組來接收。

從NIO上面咱們能看到,對於IO的兩個階段的阻塞,只是對於第一個階段有所改善,對於第二個階段在NIO裏面仍然是阻塞的。而真正的理想的異步非阻塞IO(AAIO)要作的就是,將IO操做的兩個階段都所有交給內核系統完成,用戶線程只須要告訴內核,我要讀取一塊數據,請你幫我讀取,讀取完了放在我給你的地址裏面,而後告訴我一聲就能夠了。

AIO能夠作到真正的異步的操做,但實現起來比較複雜,支持純異步IO的操做系統很是少,目前也就windows是IOCP技術實現了,而在Linux上,目前有不少開源的異步IO庫,例如libevent、libev、libuv,但基本都不是純的異步IO操做,底層仍是是使用的epoll實現的。

圖示以下:

 

NIO與Netty

既然Java擁有了各類IO體系,那麼爲何還會出現Netty這種框架呢?

Netty出現的主要緣由,以下:

(1)Java NIO類庫和API繁雜衆多,使用麻煩。

(2)Java NIO封裝程度並不高,經常須要配合Java多線程編程來使用,這是由於NIO編程涉及到Reactor模式。

(3)Java NIO異常體系不完善,如客戶端面臨斷連,重連,網絡閃斷,半包讀寫,網絡阻塞,異常碼流等問題,雖然開發相對容易,可是可靠性和穩定性並不高。

(4)Java NIO自己的bug,修復較慢。

注意,真正的異步非阻塞io,是須要操做系統層面支持的,在windows上經過IOCP實現了真正的異步io,因此Java的AIO的異步在windows平臺纔算真正獲得了支持,而在Linux系統中,仍然用的是epoll模式,因此在Linux層面上的AIO,並非真正的或者純的異步IO,這也是Netty裏面爲何採用Java的NIO實現的,而並不是是AIO,主要緣由以下:

(1)AIO在linux上底層實現仍使用EPOLL,與NIO相同,所以在性能上沒有明顯的優點

(2)Windows的AIO底層實現良好,但Netty的開發者並無把Windows做爲主要使用平臺,因此優化考慮Linux

總結

本文主要介紹了Java裏面IO模型的演變和發展,這也是Java在服務端領域大放異彩的一個重要緣由,瞭解這些知識以後,咱們再去學習高性能的Netty框架,將會更加容易。

相關文章
相關標籤/搜索