Java NIO AIO 基本概念
一、NIO
在介紹NIO程式設計之前,我們首先需要澄清一個概念:NIO到底是什麼的簡稱?有人稱之為New I/O,因為它相對於之前的I/O類庫是新增的,所以被稱為New I/O,這是它的官方叫法。但是,由於之前老的I/O類庫是阻塞I/O,New I/O類庫的目標就是要讓Java支援非阻塞I/O,所以,更多的人喜歡稱之為非阻塞I/O(Non-block I/O),由於非阻塞I/O更能夠體現NIO的特點,所以我們這裡使用NIO表示非阻塞I/O。
與Socket類和ServerSocket類相對應,NIO也提供了SocketChannel和ServerSocketChannel兩種不同的套接字通道實現。這兩種新增的通道都支援阻塞和非阻塞兩種模式。阻塞模式使用非常簡單,但是效能和可靠性都不好,非阻塞模式則正好相反。開發人員一般可以根據自己的需要來選擇合適的模式,一般來說,低負載,低併發的應用程式可以選擇同步阻塞I/O以降低程式設計複雜度,但是對於高負載,高併發的網路應用,需要使用NIO的非阻塞模式進行開發。
NIO類庫簡介
1、緩衝區Buffer
我們首先介紹緩衝區(Buffer)的概念,Buffer是一個物件,它包含一些要寫入或者要讀出的資料。在NIO類庫中加入Buffer物件,體現了新庫與原I/O的一個重要區別。在面向流的I/O中,可以將資料直接寫入或者將資料直接讀到Stream物件中。
在NIO庫中,所有資料都是用緩衝區處理的。在讀取資料時,它是直接讀到緩衝區中的;在寫入資料時,寫入到緩衝區中。任何時候訪問NIO中的資料,都是通過緩衝區進行操作。
緩衝區實質上是一個數組。通常它是一個位元組陣列(ByteBuffer),也可以使用其他種類的陣列。但是一個緩衝區不僅僅是一個數組,緩衝區提供了對資料的結構化訪問以及維護讀寫位置(limit)等資訊。
最常用的緩衝區是ByteBuffer,一個ByteBuffer提供了一組功能用於操作byte陣列。除了ByteBuffer,還有其他的一些緩衝區,事實上,每一種Java基本型別(除了Boolean型別)都對應有一種緩衝區,具體如下:
- ByteBuffer:位元組緩衝區
- CharBuffer:字元緩衝區
- ShortBuffer:短整型緩衝區
- IntBuffer:整型緩衝區
- LongBuffer:長整型緩衝區
- FloatBuffer:浮點型緩衝區
- DoubleBuffer:雙精度浮點型緩衝區
緩衝區的繼承關係如下圖所示: 每一個Buffer類都是Buffer介面的一個子例項。除了ByteBuffer,每一個Buffer類都有完全一樣的操作,只是它們所處理的資料型別不一樣。因為大多數標準I/O操作都使用ByteBuffer,所以它除了具有一般緩衝區的操作之外還提供一些特有的操作,方便網路讀寫。
2、通道Channel
Channel是一個通道,可以通過它讀取和寫入資料,它就像輸水管道一樣,網路資料通過Channel讀取和寫入。通道與流的不同之處在於通道是雙向的,流只是在一個方向上移動(一個流必須是InputStream 或者 OutputStream的子類),而且通道可以用於讀,寫或者同時用於讀寫。
因為Channel是全雙工的,所以它可以比流更好地對映底層作業系統的API。特別是在UNIX網路程式設計模型中,底層作業系統的通道都是全雙工的,同時支援讀寫操作。
Channel的整合關係如下圖所示:
自頂向下看, 前三層主要是Channel介面,用於定義它的功能,後面是一些具體的功能類(抽象類)。從類圖可以看出,實際上Channel可以分為兩大類:分別是用於網路讀寫的SelectableChannel和用於檔案操作的FileChannel。
3、多路複用器Selector
多路複用器Selector,它是Java NIO程式設計的基礎,熟練地掌握Selector對於掌握NIO程式設計至關重要。多路複用器提供選擇已經就緒的任務的能力。簡單來講, Selector會不斷地輪詢註冊在其上的Channel,如果某個Channel上面有新的TCP連線接入,讀和寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以獲取就緒Channel的集合,進行後續的I/O操作 。
一個多路複用器Selector可以同時輪詢多個Channel,由於JDK使用了epoll()代替傳統的select實現,所以它並沒有最大連線控制代碼1024/2048的限制。這也就意味著只需要一個執行緒負責Selector的輪詢,就可以接入成千上萬的客戶端,這確實是個非常巨大的進步。
4、NIO服務端序列圖
儘管NIO程式設計難度確實比同步阻塞BIO大很多,但是我們要考慮到它的優點:
- 客戶端發起的連線操作是非同步的,可以通過在多路複用器註冊OP_CONNECT等後續結果,不需要像之前的客戶端那樣被同步阻塞。
- SocketChannel的讀寫操作都是非同步的,如果沒有可讀寫的資料它不會同步等待,直接返回,這樣IO通訊執行緒就可以處理其它的鏈路,不需要同步等待這個鏈路可用。
- 執行緒模型的優化:由於JDK的Selector在Linux等主流作業系統上通過epoll實現,它沒有連線控制代碼數的限制(只受限於作業系統的最大控制代碼數或者對單個程序的控制代碼限制),這意味著一個Selector執行緒可以同時處理成千上萬個客戶端連線,而且效能不會隨著客戶端的增加而線性下降,因此,它非常適合做高效能、高負載的網路伺服器。
二、AIO
JDK1.7升級了NIO類庫,升級後的NIO類庫被稱為NIO2.0。也就是我們要介紹的AIO。NIO2.0引入了新的非同步通道的概念,並提供了非同步檔案通道和非同步套接字通道的實現。非同步通道提供兩種方式獲取操作結果。
- 通過Java.util.concurrent.Future類來表示非同步操作的結果;
- 在執行非同步操作的時候傳入一個Java.nio.channels. CompletionHandler介面的實現類作為操作完成的回撥。
NIO2.0的非同步套接字通道是真正的非同步非阻塞IO,它對應UNIX網路程式設計中的事件驅動IO(AIO),它不需要通過多路複用器(Selector)對註冊的通道進行輪詢操作即可實現非同步讀寫,從而簡化了NIO的程式設計模型。
我們可以得出結論:非同步Socket Channel是被動執行物件,我們不需要像NIO程式設計那樣建立一個獨立的IO執行緒來處理讀寫操作。對於AsynchronousServerSocketChannel和AsynchronousSocketChannel,它們都由JDK底層的執行緒池負責回撥並驅動讀寫操作。正因為如此,基於NIO2.0新的非同步非阻塞Channel進行程式設計比NIO程式設計更為簡單。
個人微信公眾號: