1. 程式人生 > 其它 >BIO NIO AIO

BIO NIO AIO

在學習Java I/O類庫時,容易混淆NIO、BIO、AIO這幾個概念,同時對於阻塞和非阻塞、同步和非同步的理解也較為晦澀,這篇文章是對這幾個概念的一些區分以及個人的一些見解。

BIO 是同步阻塞通訊。

NIO 是同步非阻塞通訊。

AIO 是徹底的非同步通訊。

有一個經典的舉例。燒開水。

假設有這麼一個場景,有一排水壺(客戶)在燒水。

AIO的做法是,每個水壺上裝一個開關,當水開了以後會提醒對應的執行緒去處理。

NIO的做法是,叫一個執行緒不停的迴圈觀察每一個水壺,根據每個水壺當前的狀態去處理。

BIO的做法是,叫一個執行緒停留在一個水壺那,直到這個水壺燒開,才去處理下一個水壺。

一、同步阻塞I/O(BIO)

Blocking(同步阻塞)I/O

  • 伺服器實現模式為一個連線一個執行緒
  • 即客戶端有連線請求時伺服器就需要啟動一個執行緒進行處理。
  • 如果這個連線不做任何事情會造成不必要的執行緒開銷,可以通過執行緒池機制來改善。
  • BIO是傳統的Java io程式設計,其相關的類和介面在java.io 包下
  • BIO方式適用於連線數目比較小且固定的架構,這種方式對服務端資源要求比較高,併發侷限於應用中,在jdk1.4以前是唯一的IO實現。
  • 程式直觀簡單易理解。

BIO程式設計流程

  1. 伺服器端啟動一個SeverSocket
  2. 客戶端啟動Socket對伺服器端發起通訊,預設情況下伺服器端需為每個客戶端建立一個執行緒與之通訊
  3. 客戶端發起請求後,先諮詢伺服器端是否有執行緒響應,如果沒有則會等待或被拒絕
  4. 如果有執行緒響應,客戶端執行緒會等待請求結束後,再繼續執行

二、同步非阻塞I/O(NIO)

Non-blocking I/O,在Java領域,也稱為New I/O

  • 伺服器實現模式為一個請求一個執行緒

  • 客戶端傳送的連線請求都會註冊到多路複用器 (Selector選擇器)上,多路複用器(Selector選擇器)輪詢到連線有IO請求時才啟動一個執行緒進行處理。

  • NIO的相關類都放在java.nio包或其子包下,並對原先java.io包中許多類進行了改寫。

  • NIO方式適用於連線數目多且連線比較短(輕操作)的架構,比如聊天伺服器,併發侷限於應用中。

  • 程式設計比較複雜,jdk1,4開始支援。

NIO三大核心

緩衝區(Buffer),通道(Channel),選擇器(Selector)

緩衝區(Buffer)

  • NIO是面向緩衝區, 或者說是面向塊程式設計的。在NIO的IO傳輸中,資料會先讀入到緩衝區,當需要時再從緩衝區寫出,這樣減少了直接讀寫磁碟的次數,提高了IO傳輸的效率。

  • 緩衝區(buffer)本質上是一個可以讀寫資料的記憶體塊,即在記憶體空間中預留了一定的儲存空間,這些儲存空間用來緩衝輸入和輸出的資料,這部分預留的儲存空間就叫緩衝區。

  • 在NIO程式中,通道channel雖然負責資料的傳輸,但是輸入和輸出的資料都必須經過緩衝區buffer。

  • 在java中,緩衝區的相關類都在java.nio包下,其最頂層的類是 Buffer,它是一個抽象類。

Buffer類的4個重要屬性:

  • mark:標記
  • position:位置,下一個要被讀或寫的元素的索引,每次讀寫緩衝區都會改變該值,為下次讀寫做準備
  • limit:表示緩衝區的終點,不能對緩衝區中超過極限的位置進行讀寫操作,且極限是可修改的
  • capacity:容量,即緩衝區的最多可容納的資料量,該值在建立緩衝區時被設立,且不可修改

通道(Channel)

在NIO程式中伺服器端和客戶端之間的資料讀寫不是通過流,而是通過通道來讀寫的。

通道類似於流,都是用來讀寫資料的,但它們之間也是有區別的:

  • 通道是雙向的,即可以讀也可以寫,而流是單向的,只能讀或寫

  • 通道可以實現非同步讀寫資料

  • 通道可以從緩衝區讀資料,也可以把資料寫入緩衝區

  • java中channel的相關類在java.nio.channel包下。Channel是一個介面。

  • 常用的實現類如下:

    • FileChannel:用於檔案的資料讀寫,其真正的實現類為FileChannelImpl

    • DatagramChannel:用於UDP的資料讀寫,其真正的實現類為DatagramChannelImpl

    • ServerSocketChannel:用於監聽TCP連線,每當有客戶端連線時都會建立一個SocketChannel,功能類似ServerSocket,其真正的實現類為ServerSocketChannelImpl

    • SocketChannel:用於TCP的資料讀寫,功能類似節點流+Socket,其真正的實現類為SocketChannelImpl

選擇器(Selector)

  • 在NIO程式中,可以用選擇器Selector實現一個選擇器處理多個通道,即一個執行緒處理多個連線

  • 只要把通道註冊到Selector上,就可以通過Selector來監測通道,如果通道有事件發生,便獲取事件通道然後針對每個事件進行相應的處理。這樣,只有在通道(連線)有真正的讀/寫事件發生時,才會進行讀寫操作,大大減少了系統開銷,並且不必為每個連線建立單獨執行緒,就不用去維護過多的執行緒。

  • 選擇器的相關類在java.nio.channels包和其子包下,頂層類是zSelector,它是一個抽象類。

每個註冊到選擇器的通道都需定義需進行的操作事件型別,通過檢視SelectionKey類的屬性可以知道操作事件的型別有4種:

public static final int OP_READ = 1 << 0; //讀操作
public static final int OP_WRITE = 1 << 2; //寫操作
public static final int OP_CONNECT = 1 << 3; //連線操作
public static final int OP_ACCEPT = 1 << 4; //接收操作
選擇器的檢查

我們可以通過選擇器的檢查方法,如select()來得知發生事件的通道數量,當該數量大於為0時,即至少有一個通道發生了事件,就可以使用selectedKeys()方法來獲取所有發生事件的通道對應的SelectionKey,通過SelectionKey中的方法來判斷對應通道中需處理的事件型別是什麼,在根據事件做出相應的處理。

public final boolean isReadable() { //判斷是否是讀操作
    return (readyOps() & OP_READ) != 0;
}

public final boolean isWritable() { //判斷是否是寫操作
    return (readyOps() & OP_WRITE) != 0;
}

public final boolean isConnectable() { //判斷是否是連線操作
    return (readyOps() & OP_CONNECT) != 0;
}

public final boolean isAcceptable() { //判斷是否是接收操作
    return (readyOps() & OP_ACCEPT) != 0;
}

三、非同步非阻塞I/O(AIO)

Asynchronization I/O 非同步非阻塞I/O

一般來說,伺服器端的I/O主要有兩種情況:一是來自網路的I/O;二是對檔案(裝置)的I/O。

Windows的非同步I/O模型能很好的適用於這兩種情況。

Linux針對前者提供了epoll模型,針對後者提供了AIO模型(關於是否把兩者統一起來爭論了很久)。

  • Linux Native AIO 伺服器實現模式為一個有效請求一個執行緒

  • AIO是真正的非同步IO操作:允許程序發起很多I/O操作,而不用阻塞或等待任何操作完成。

  • AIO的基本思想:

    • 允許程序發起很多I/O操作,而不用阻塞或等待任何操作完成,稍後或在接收到I/O操作完成通知時,程序可以檢索I/O操作結果
    • 在非同步非阻塞I/O中,我們可以同時發起多個傳輸操作,這需要每個傳輸操作都有唯一的上下文,這樣我們才能在他們完成時區分到底是哪個傳輸操作完成了,這個工作可以通過aiocb結構體進行區分。

客戶端的IO請求都是由作業系統先完成了再通知伺服器用其啟動執行緒進行處理。

由於作業系統的Read和Write操作是非同步的,所以AIO操作是真正非同步的。

AIO方式適用於連線數目多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫OS參與併發操作。

程式設計比較複雜,jdk1.7開始支援。

四、IO與NIO區別

IO面向流,NIO面向緩衝區

IO的各種流是阻塞的,NIO是非阻塞模式

Java NIO的選擇允許一個單獨的執行緒來監視多個輸入通道,可以註冊多個通道使用一個選擇器,然後使用一個單獨的執行緒來“選擇”通道:這些通道里已經有可以處理的輸入或選擇已準備寫入的通道。這種選擇機制,使得一個單獨的執行緒很容易來管理多個通道

五、同步與非同步的區別

同步:傳送一個請求,等待返回,再發送下一個請求,同步可以避免出現死鎖,髒讀的發生。

非同步:傳送一個請求,不等待返回,隨時可以再發送下一個請求,可以提高效率,保證併發。

同步非同步關注點在於訊息通訊機制

阻塞與非阻塞關注的是程式在等待呼叫結果時(訊息、返回值)的狀態

  • 阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起。呼叫執行緒只有在得到結果之後才會返回。

  • 非阻塞呼叫指在不能立刻得到結果之前,該呼叫不會阻塞當前執行緒

不同層次

CPU層次:作業系統進行IO或任務排程層次,現代作業系統通常使用非同步非阻塞方式進行IO(有少部分IO可能會使用同步非阻塞),即發出IO請求後,並不等待IO操作完成,而是繼續執行接下來的指令(非阻塞),IO操作和CPU指令互不干擾(非同步),最後通過中斷的方式通知IO操作的完成結果。

執行緒層次:作業系統排程單元的層次,作業系統為了減輕程式設計師的思考負擔,將底層的非同步非阻塞的IO方式進行封裝,把相關係統呼叫(如read和write)以同步的方式展現出來,然而同步阻塞IO會使執行緒掛起,同步非阻塞IO會消耗CPU資源在輪詢上,3個解決方法;

多執行緒(同步阻塞)

IO多路複用(select、poll、epoll)

直接暴露出非同步的IO介面,kernel-aio和IOCP(非同步非阻塞)

Linux IO模型

阻塞/非阻塞:等待I/O完成的方式,阻塞要求使用者程式停止執行,直到IO完成,而非阻塞在IO完成之前還可以繼續執行。

同步/非同步:獲知IO完成的方式,同步需要時刻關心IO是否完成,非同步無需主動關心,在IO完成時它會收到通知。