我們首先得明白什么是同步,異步,阻塞,非阻塞,只有這幾個單個概念理解清楚了,然后在組合理解起來,就相對比較容易了。
IO模型主要分類:
- 同步(synchronous) IO和異步(asynchronous) IO
- 阻塞(blocking) IO和非阻塞(non-blocking)IO
- 同步阻塞(blocking-IO)簡稱BIO
- 同步非阻塞(non-blocking-IO)簡稱NIO
- 異步非阻塞(asynchronous-non-blocking-IO)簡稱AIO
1.BIO (同步阻塞I/O模式)
數據的讀取寫入必須阻塞在一個線程內等待其完成。
這里使用那個經典的燒開水例子,這里假設一個燒開水的場景,有一排水壺在燒開水,BIO的工作模式就是, 叫一個線程停留在某一個水壺那里,直到這個水壺燒開,才去處理下一個水壺。但是實際上線程在等待水壺燒開的時間段什么都沒有做。
2.NIO(同步非阻塞)
同時支持阻塞與非阻塞模式,但這里我們以其同步非阻塞I/O模式來說明,那么什么叫做同步非阻塞?如果還拿燒開水來說,NIO的做法是叫一個線程不斷的輪詢每個水壺的狀態,看看是否有水壺的狀態發生了改變,從而進行下一步的操作。
3.AIO (異步非阻塞I/O模型)
即NIO2 ,在Java 7提出,NIO的升級版。實現非阻塞異步的通信模式。
異步非阻塞與同步非阻塞的區別在哪里?異步非阻塞無需一個線程去輪詢所有IO操作的狀態改變,在相應的狀態改變后,系統會通知對應的線程來處理。對應到燒開水中就是,為每個水壺上面裝了一個開關,水燒開之后,水壺會自動通知我水燒開了。
IO與NIO區別
5.同步與異步的區別
- 同步
發送一個請求,等待返回,再發送下一個請求,同步可以避免出現死鎖,臟讀的發生。
- 異步
發送一個請求,不等待返回,隨時可以再發送下一個請求,可以提高效率,保證並發。
6.阻塞和非阻塞
- 阻塞
傳統的IO流都是阻塞式的。也就是說,當一個線程調用read()或者write()方法時,該線程將被阻塞,直到有一些數據讀讀取或者被寫入,在此期間,該線程不能執行其他任何任務。在完成網絡通信進行IO操作時,由於線程會阻塞,所以服務器端必須為每個客戶端都提供一個獨立的線程進行處理,當服務器端需要處理大量的客戶端時,性能急劇下降。
- 非阻塞
JavaNIO是非阻塞式的。當線程從某通道進行讀寫數據時,若沒有數據可用時,該線程會去執行其他任務。線程通常將非阻塞IO的空閑時間用於在其他通道上執行IO操作,所以單獨的線程可以管理多個輸入和輸出通道。因此NIO可以讓服務器端使用一個或有限幾個線程來同時處理連接到服務器端的所有客戶端。
7.BIO、NIO、AIO適用場景
- BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,並發局限於應用中,JDK1.4以前的唯一選擇。
- NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,並發局限於應用中,編程比較復雜。
- AIO方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與並發操作,編程比較復雜,JDK7開始支持。
NIO的3個核心概念
NIO重點是把Channel(通道),Buffer(緩沖區),Selector(選擇器)三個類之間的關系弄清楚。
1.緩沖區Buffer
Buffer是一個對象。它包含一些要寫入或者讀出的數據。在面向流的I/O中,可以將數據寫入或者將數據直接讀到Stream對象中。
在NIO中,所有的數據都是用緩沖區處理。這也就本文上面談到的IO是面向流的,NIO是面向緩沖區的。
緩沖區實質是一個數組,通常它是一個字節數組(ByteBuffer),也可以使用其他類的數組。但是一個緩沖區不僅僅是一個數組,緩沖區提供了對數據的結構化訪問以及維護讀寫位置(limit)等信息。
最常用的緩沖區是ByteBuffer,一個ByteBuffer提供了一組功能於操作byte數組。除了ByteBuffer,還有其他的一些緩沖區,事實上,每一種Java基本類型(除了Boolean)都對應一種緩沖區,具體如下:
- ByteBuffer:字節緩沖區
- CharBuffer:字符緩沖區
- ShortBuffer:短整型緩沖區
- IntBuffer:整型緩沖區
- LongBuffer:長整型緩沖區
- FloatBuffer:浮點型緩沖區
- DoubleBuffer:雙精度浮點型緩沖區
2.通道Channel
Channel是一個通道,可以通過它讀取和寫入數據,他就像自來水管一樣,網絡數據通過Channel讀取和寫入。
通道和流不同之處在於通道是雙向的,流只是在一個方向移動,而且通道可以用於讀,寫或者同時用於讀寫。
因為Channel是全雙工的,所以它比流更好地映射底層操作系統的API,特別是在UNIX網絡編程中,底層操作系統的通道都是全雙工的,同時支持讀和寫。
Channel有四種實現:
- FileChannel:是從文件中讀取數據。
- DatagramChannel:從UDP網絡中讀取或者寫入數據。
- SocketChannel:從TCP網絡中讀取或者寫入數據。
- ServerSocketChannel:允許你監聽來自TCP的連接,就像服務器一樣。每一個連接都會有一個SocketChannel產生。
3.多路復用器Selector
Selector選擇器可以監聽多個Channel通道感興趣的事情(read、write、accept(服務端接收)、connect,實現一個線程管理多個Channel,節省線程切換上下文的資源消耗。Selector只能管理非阻塞的通道,FileChannel是阻塞的,無法管理。
關鍵對象
- Selector:選擇器對象,通道注冊、通道監聽對象和Selector相關。
- SelectorKey:通道監聽關鍵字,通過它來監聽通道狀態。
監聽注冊
監聽注冊在Selector
socketChannel.register(selector, SelectionKey.OP_READ);
監聽的事件有
- OP_ACCEPT: 接收就緒,serviceSocketChannel使用的
- OP_READ: 讀取就緒,socketChannel使用
- OP_WRITE: 寫入就緒,socketChannel使用
- OP_CONNECT: 連接就緒,socketChannel使用