聊聊BIO、NIO與AIO的區別

題目:說一下BIO/AIO/NIO 有什麼區別?及異步模式的用途和意義?

1F

說一說I/O
首先來講一下什麼是I/O?java

在計算機系統中I/O就是輸入(Input)和輸出(Output)的意思,針對不一樣的操做對象,能夠劃分爲磁盤I/O模型,網絡I/O模型,內存映射I/O, Direct I/O、數據庫I/O等,只要具備輸入輸出類型的交互系統均可以認爲是I/O系統,也能夠說I/O是整個操做系統數據交換與人機交互的通道,這個概念與選用的開發語言沒有關係,是一個通用的概念。程序員

在現在的系統中I/O卻擁有很重要的位置,如今系統都有可能處理大量文件,大量數據庫操做,而這些操做都依賴於系統的I/O性能,也就形成了如今系統的瓶頸每每都是因爲I/O性能形成的。所以,爲了解決磁盤I/O性能慢的問題,系統架構中添加了緩存來提升響應速度;或者有些高端服務器從硬件級入手,使用了固態硬盤(SSD)來替換傳統機械硬盤;在大數據方面,Spark愈來愈多的承擔了實時性計算任務,而傳統的Hadoop體系則大多應用在了離線計算與大量數據存儲的場景,這也是因爲磁盤I/O性能遠不如內存I/O性能而形成的格局(Spark更多的使用了內存,而MapReduece更多的使用了磁盤)。所以,一個系統的優化空間,每每都在低效率的I/O環節上,不多看到一個系統CPU、內存的性能是其整個系統的瓶頸。也正由於如此,Java在I/O上也一直在作持續的優化,從JDK 1.4開始便引入了NIO模型,大大的提升了以往BIO模型下的操做效率。web

這裏先給出BIO、NIO、AIO的基本定義與類比描述:數據庫

BIO (Blocking I/O):同步阻塞I/O模式,數據的讀取寫入必須阻塞在一個線程內等待其完成。這裏使用那個經典的燒開水例子,這裏假設一個燒開水的場景,有一排水壺在燒開水,BIO的工做模式就是, 叫一個線程停留在一個水壺那,直到這個水壺燒開,纔去處理下一個水壺。可是實際上線程在等待水壺燒開的時間段什麼都沒有作。編程

NIO (New I/O):同時支持阻塞與非阻塞模式,但這裏咱們以其同步非阻塞I/O模式來講明,那麼什麼叫作同步非阻塞?若是還拿燒開水來講,NIO的作法是叫一個線程不斷的輪詢每一個水壺的狀態,看看是否有水壺的狀態發生了改變,從而進行下一步的操做。windows

AIO ( Asynchronous I/O):異步非阻塞I/O模型。異步非阻塞與同步非阻塞的區別在哪裏?異步非阻塞無需一個線程去輪詢全部IO操做的狀態改變,在相應的狀態改變後,系統會通知對應的線程來處理。對應到燒開水中就是,爲每一個水壺上面裝了一個開關,水燒開以後,水壺會自動通知我水燒開了。數組

進程中的IO調用步驟大體能夠分爲如下四步:緩存

進程向操做系統請求數據 ;服務器

操做系統把外部數據加載到內核的緩衝區中;網絡

操做系統把內核的緩衝區拷貝到進程的緩衝區 ;

進程得到數據完成本身的功能 ;

當操做系統在把外部數據放到進程緩衝區的這段時間(即上述的第二,三步),若是應用進程是掛起等待的,那麼就是同步IO,反之,就是異步IO,也就是AIO 。

2F

BIO(Blocking I/O)同步阻塞I/O
這是最基本與簡單的I/O操做方式,其根本特性是作完一件事再去作另外一件事,一件事必定要等前一件事作完,這很符合程序員傳統的順序來開發思想,所以BIO模型程序開發起來較爲簡單,易於把握。

可是BIO若是須要同時作不少事情(例如同時讀不少文件,處理不少tcp請求等),就須要系統建立不少線程來完成對應的工做,由於BIO模型下一個線程同時只能作一個工做,若是線程在執行過程當中依賴於須要等待的資源,那麼該線程會長期處於阻塞狀態,咱們知道在整個操做系統中,線程是系統執行的基本單位,在BIO模型下的線程 阻塞就會致使系統線程的切換,從而對整個系統性能形成必定的影響。固然若是咱們只須要建立少許可控的線程,那麼採用BIO模型也是很好的選擇,但若是在須要考慮高併發的web或者tcp服務器中採用BIO模型就沒法應對了,若是系統開闢成千上萬的線程,那麼CPU的執行時機都會浪費在線程的切換中,使得線程的執行效率大大下降。此外,關於線程這裏說一句題外話,在系統開發中線程的生命週期必定要準確控制,在須要必定規模併發的情形下,儘可能使用線程池來確保線程建立數目在一個合理的範圍以內,切莫編寫線程數量建立上限的代碼。

3F

NIO (New I/O) 同步非阻塞I/O
關於NIO,國內有不少技術博客將英文翻譯成No-Blocking I/O,非阻塞I/O模型 ,固然這樣就與BIO造成了鮮明的特性對比。NIO自己是基於事件驅動的思想來實現的,其目的就是解決BIO的大併發問題,在BIO模型中,若是須要併發處理多個I/O請求,那就須要多線程來支持,NIO使用了多路複用器機制,以socket使用來講,多路複用器經過不斷輪詢各個鏈接的狀態,只有在socket有流可讀或者可寫時,應用程序才須要去處理它,在線程的使用上,就不須要一個鏈接就必須使用一個處理線程了,而是隻是有效請求時(確實須要進行I/O處理時),纔會使用一個線程去處理,這樣就避免了BIO模型下大量線程處於阻塞等待狀態的情景。

相對於BIO的流,NIO抽象出了新的通道(Channel)做爲輸入輸出的通道,而且提供了緩存(Buffer)的支持,在進行讀操做時,須要使用Buffer分配空間,而後將數據從Channel中讀入Buffer中,對於Channel的寫操做,也須要現將數據寫入Buffer,而後將Buffer寫入Channel中。

以下是NIO方式進行文件拷貝操做的示例,見下圖:
在這裏插入圖片描述
經過比較New IO的使用方式咱們能夠發現,新的IO操做再也不面向 Stream來進行操做了,改成了通道Channel,而且使用了更加靈活的緩存區類Buffer,Buffer只是緩存區定義接口, 根據須要,咱們能夠選擇對應類型的緩存區實現類。在java NIO編程中,咱們須要理解如下3個對象Channel、Buffer和Selector。

  • Channel

首先說一下Channel,國內大多翻譯成「通道」。Channel和IO中的Stream(流)是差很少一個等級的。只不過Stream是單向的,譬如:InputStream, OutputStream。而Channel是雙向的,既能夠用來進行讀操做,又能夠用來進行寫操做,NIO中的Channel的主要實現有:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel;經過看名字就能夠猜出個因此然來:分別能夠對應文件IO、UDP和TCP(Server和Client)。

  • Buffer

NIO中的關鍵Buffer實現有:ByteBuffer、CharBuffer、DoubleBuffer、 FloatBuffer、IntBuffer、 LongBuffer,、ShortBuffer,分別對應基本數據類型: byte、char、double、 float、int、 long、 short。固然NIO中還有MappedByteBuffer, HeapByteBuffer, DirectByteBuffer等這裏先不具體陳述其用法細節。

說一下 DirectByteBuffer 與 HeapByteBuffer 的區別?

它們 ByteBuffer 分配內存的兩種方式。HeapByteBuffer 顧名思義其內存空間在 JVM 的 heap(堆)上分配,能夠看作是 jdk 對於 byte[] 數組的封裝;而 DirectByteBuffer 則直接利用了系統接口進行內存申請,其內存分配在c heap 中,這樣就減小了內存之間的拷貝操做,如此一來,在使用 DirectByteBuffer 時,系統就能夠直接從內存將數據寫入到 Channel 中,而無需進行 Java 堆的內存申請,複製等操做,提升了性能。既然如此,爲何不直接使用 DirectByteBuffer,還要來個 HeapByteBuffer?緣由在於, DirectByteBuffer 是經過full gc來回收內存的,DirectByteBuffer會本身檢測狀況而調用 system.gc(),可是若是參數中使用了 DisableExplicitGC 那麼就沒法回收該快內存了,-XX:+DisableExplicitGC標誌自動將 System.gc() 調用轉換成一個空操做,就是應用中調用 System.gc() 會變成一個空操做,那麼若是設置了就須要咱們手動來回收內存了,因此DirectByteBuffer使用起來相對於徹底託管於 java 內存管理的Heap ByteBuffer 來講更復雜一些,若是用很差可能會引發OOM。Direct ByteBuffer 的內存大小受 -XX:MaxDirectMemorySize JVM 參數控制(默認大小64M),在 DirectByteBuffer 申請內存空間達到該設置大小後,會觸發 Full GC。

  • Selector

Selector 是NIO相對於BIO實現多路複用的基礎,Selector 運行單線程處理多個 Channel,若是你的應用打開了多個通道,但每一個鏈接的流量都很低,使用 Selector 就會很方便。例如在一個聊天服務器中。要使用 Selector , 得向 Selector 註冊 Channel,而後調用它的 select() 方法。這個方法會一直阻塞到某個註冊的通道有事件就緒。一旦這個方法返回,線程就能夠處理這些事件,事件的例子有如新的鏈接進來、數據接收等。

這裏咱們再來看一個NIO模型下的TCP服務器的實現,咱們能夠看到Selector 正是NIO模型下 TCP Server 實現IO複用的關鍵,請仔細理解下段代碼while循環中的邏輯,見下圖:
在這裏插入圖片描述

4F

AIO (Asynchronous I/O) 異步非阻塞I/O
Java AIO就是Java做爲對異步IO提供支持的NIO.2 ,Java NIO2 (JSR 203)定義了更多的 New I/O APIs, 提案2003提出,直到2011年才發佈, 最終在JDK 7中才實現。JSR 203除了提供更多的文件系統操做API(包括可插拔的自定義的文件系統), 還提供了對socket和文件的異步 I/O操做。 同時實現了JSR-51提案中的socket channel所有功能,包括對綁定, option配置的支持以及多播multicast的實現。

從編程模式上來看AIO相對於NIO的區別在於,NIO須要使用者線程不停的輪詢IO對象,來肯定是否有數據準備好能夠讀了,而AIO則是在數據準備好以後,纔會通知數據使用者,這樣使用者就不須要不停地輪詢了。固然AIO的異步特性並非Java實現的僞異步,而是使用了系統底層API的支持,在Unix系統下,採用了epoll IO模型,而windows即是使用了IOCP模型。關於Java AIO,本篇只作一個拋磚引玉的介紹,若是你在實際工做中用到了,那麼能夠參考Netty在高併發下使用AIO的相關技術。

總 結

IO實質上與線程沒有太多的關係,可是不一樣的IO模型改變了應用程序使用線程的方式,NIO與AIO的出>現解決了不少BIO沒法解決的併發問題,固然任何技術拋開適用場景都是耍流氓,複雜的技術每每是爲了解決簡單技術沒法解決的問題而設計的,在系統開發中能用常規技術解決的問題,毫不用複雜技術,>不然大大增長系統代碼的維護難度,學習IT技術不是爲了炫技,而是要實實在在解決問題。

原文地址:

https://juejin.im/entry/598da7d16fb9a03c42431ed3

相關文章
相關標籤/搜索