初識NIO:linux
在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 類, 引入了一種基於通道和緩衝區的 I/O 方式,它可使用 Native 函數庫直接分配堆外內存,而後經過一個存儲在 Java 堆的 DirectByteBuffer 對象做爲這塊內存的引用進行操做,避免了在 Java 堆和 Native 堆中來回複製數據。windows
NIO 是一種同步非阻塞的 IO 模型。同步是指線程不斷輪詢 IO 事件是否就緒,非阻塞是指線程在等待 IO 的時候,能夠同時作其餘任務。同步的核心就是 Selector,Selector 代替了線程自己輪詢 IO 事件,避免了阻塞同時減小了沒必要要的線程消耗;非阻塞的核心就是通道和緩衝區,當 IO 事件就緒時,能夠經過寫道緩衝區,保證 IO 的成功,而無需線程阻塞式地等待。服務器
Buffer:框架
爲何說NIO是基於緩衝區的IO方式呢?由於,當一個連接創建完成後,IO的數據未必會立刻到達,爲了當數據到達時可以正確完成IO操做,在BIO(阻塞IO)中,等待IO的線程必須被阻塞,以全天候地執行IO操做。爲了解決這種IO方式低效的問題,引入了緩衝區的概念,當數據到達時,能夠預先被寫入緩衝區,再由緩衝區交給線程,所以線程無需阻塞地等待IO。tcp
通道:函數
當執行:SocketChannel.write(Buffer),便將一個 buffer 寫到了一個通道中。若是說緩衝區還好理解,通道相對來講就更加抽象。網上博客不免有寫不嚴謹的地方,容易使初學者感到難以理解。oop
引用 Java NIO 中權威的說法:通道是 I/O 傳輸發生時經過的入口,而緩衝區是這些數 據傳輸的來源或目標。對於離開緩衝區的傳輸,您想傳遞出去的數據被置於一個緩衝區,被傳送到通道。對於傳回緩衝區的傳輸,一個通道將數據放置在您所提供的緩衝區中。優化
例如 有一個服務器通道 ServerSocketChannel serverChannel,一個客戶端通道 SocketChannel clientChannel;服務器緩衝區:serverBuffer,客戶端緩衝區:clientBuffer。操作系統
當服務器想向客戶端發送數據時,須要調用:clientChannel.write(serverBuffer)。當客戶端要讀時,調用 clientChannel.read(clientBuffer).net
當客戶端想向服務器發送數據時,須要調用:serverChannel.write(clientBuffer)。當服務器要讀時,調用 serverChannel.read(serverBuffer)
這樣,通道和緩衝區的關係彷佛更好理解了。在實踐中,未必會出現這種雙向鏈接的蠢事(然而這確實存在的,後面的內容還會涉及),可是能夠理解爲在NIO中:若是想將Data發到目標端,則須要將存儲該Data的Buffer,寫入到目標端的Channel中,而後再從Channel中讀取數據到目標端的Buffer中。
Selector:
通道和緩衝區的機制,使得線程無需阻塞地等待IO事件的就緒,可是老是要有人來監管這些IO事件。這個工做就交給了selector來完成,這就是所謂的同步。
Selector容許單線程處理多個 Channel。若是你的應用打開了多個鏈接(通道),但每一個鏈接的流量都很低,使用Selector就會很方便。
要使用Selector,得向Selector註冊Channel,而後調用它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒,這就是所說的輪詢。一旦這個方法返回,線程就能夠處理這些事件。
Selector中註冊的感興趣事件有:
OP_ACCEPT
OP_CONNECT
OP_READ
OP_WRITE
優化:
一種優化方式是:將Selector進一步分解爲Reactor,將不一樣的感興趣事件分開,每個Reactor只負責一種感興趣的事件。這樣作的好處是:一、分離阻塞級別,減小了輪詢的時間;二、線程無需遍歷set以找到本身感興趣的事件,由於獲得的set中僅包含本身感興趣的事件。
NIO和epoll:
epoll是Linux內核的IO模型。我想必定有人想問,AIO聽起來比NIO更加高大上,爲何不使用AIO?AIO其實也有應用,可是有一個問題就是,Linux是不支持AIO的,所以基於AIO的程序運行在Linux上的效率相比NIO反而更低。而Linux是最主要的服務器OS,所以相比AIO,目前NIO的應用更加普遍。
說到這裏,可能你已經明白了,epoll必定和NIO有着很深的因緣。沒錯,若是仔細研究epoll的技術內幕,你會發現它確實和NIO很是類似,都是基於「通道」和緩衝區的,也有selector,只是在epoll中,通道其實是操做系統的「管道」。和NIO不一樣的是,NIO中,解放了線程,可是須要由selector阻塞式地輪詢IO事件的就緒;而epoll中,IO事件就緒後,會自動發送消息,通知selector:「我已經就緒了。」能夠認爲,Linux的epoll是一種效率更高的NIO。
NIO軼事:
一篇有意思的博客,講的 Java selector.open() 的時候,會建立一個本身和本身的連接(windows上是tcp,linux上是通道)
這麼作的緣由:能夠從 Apache Mina 中窺探。在 Mina 中,有以下機制:
Mina框架會建立一個Work對象的線程。
Work對象的線程的run()方法會從一個隊列中拿出一堆Channel,而後使用Selector.select()方法來偵聽是否有數據能夠讀/寫。
最關鍵的是,在select的時候,若是隊列有新的Channel加入,那麼,Selector.select()會被喚醒,而後從新select最新的Channel集合。
要喚醒select方法,只須要調用Selector的wakeup()方法。
而一個阻塞在select上的線程有如下三種方式能夠被喚醒:
有數據可讀/寫,或出現異常。
阻塞時間到,即time out。
收到一個non-block的信號。可由kill或pthread_kill發出。
首先 2 能夠排除,而第三種方式,只在linux中存在。所以,Java NIO爲何要建立一個本身和本身的連接:就是若是想要喚醒select,只須要朝着本身的這個loopback鏈接發點數據過去,因而,就能夠喚醒阻塞在select上的線程了。