NIO
- NIO 是一種同步非阻塞的I/O模型
NIO的機制
核心包含三個方面
- Buffer
- Channel
- Selector
Buffer:在NIO中,數據總是從通道(Channel)到緩沖區(qū)(Buffer)扼鞋,又或者是從Buffer寫到Channel中诵原。緩沖區(qū)是一塊可以寫入數據,然后可以從中讀取數據的內存顷蟀,這塊內存被包裝成NIO Buffer對象酒请,用以方便訪問該內存塊。
Channel:傳統(tǒng)的IO處理數據是以字節(jié)流或者字符流的方式來處理的鸣个,而NIO是以Channel來處理數據的羞反,和流的主要區(qū)別在于:一般的流(InputStream 或者OutputStream)是單向的,而Channel既可以讀囤萤,又可以寫昼窗,還能異步的寫與讀。Channel一般封裝了Socket, ByteBuffer等對象涛舍,所以Channel可以看成一個網絡連接澄惊。
Selector:Selector是Java NIO中能夠檢測到一到多個NIO Channel, 并能夠知曉Channel是否為諸如讀寫事件做好準備的組件富雅。這樣一個線程可以管理多個Channel掸驱,從而管理多個網絡連接。
三者之間的對應關系是:
NIO 的線程模型 Reactor
首先是一個前提:CPU的處理速度遠遠高于IO速度
所有的 I/O操作都可以分為兩個部分---等待就緒和讀寫操作
在傳統(tǒng)的BIO模型中没佑,其阻塞就在整個I/O的過程中全部阻塞毕贼,當socket調用了read()或者write()時,線程就會一直堵塞住蛤奢,但是這時候CPU是沒有釋放的鬼癣,但是也沒有進行任何的處理或者計算,所以這時候的CPU是被浪費掉的远剩,而一般來說讀寫操作是非晨勰纾快速的,近乎可以忽略不計瓜晤,所以對于BIO來說锥余,絕大部分時間CPU是處于“空等”的狀態(tài),造成了很大的浪費痢掠,而BIO的性能瓶頸就在這里驱犹。
NIO的非阻塞體現在第一階段(等待就緒)嘲恍,與BIO不同的是,這個時候會采用一種輪詢的向操作系統(tǒng)詢問是否有IO操作到達雄驹,如果有就進行讀寫操作佃牛,沒有也會立即返回,不會阻塞在等待IO的過程中医舆,這樣就可以將CPU釋放去進行其他的計算俘侠。這樣使得線程完全不需要等待IO,就可以完成上千上萬的連接處理請求蔬将。
下面一張圖比較形象直觀表達了這一點:
什么是Reactor模型?
按照wiki的說法是
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexesthe incoming requests and dispatches them synchronously to the associated request handlers
簡單說來就是一種事件驅動的模式爷速,有一個或者多個輸入源,并且有一個Service handler將這些請求分發(fā)給相應的Request handler霞怀。
從上面那張圖就可以看出來惫东,無論怎樣,總需要一個線程來發(fā)現是否有IO操作毙石,在NIO里廉沮,主要是Selector的來做這個工作
NIO的Reactor模型主要思想是回調,即事件驅動徐矩。一開始的時候向一個線程(即Selector)注冊事件(一般來說是讀寫或者連接事件)滞时,當IO就緒時,這個線程會產生對應一個事件滤灯,然后交由對應的handler進行處理漂洋。
Reactor的單線程和多線程模型主要區(qū)別點在于
- 單線程模型只有NIO線程對所有的請求連接,讀寫操作進行處理力喷,而多線程模型是一組
- 多線程模型是專門有一個NIO線程-Acceptor線程用于監(jiān)聽服務端,接收客戶端的請求
NIO和BIO的區(qū)別
- NIO是同步非阻塞的IO方式演训,BIO是同步并阻塞的IO方式
- NIO編程復雜弟孟,BIO編程簡單
- NIO的同步非阻塞實現方式為設置對與客戶端的每個連接都是都會先注冊到多路復用器(關于多路復用器),多路復用輪詢到有IO請求時才啟動一個線程來處理,而BIO是比較簡單的線程阻塞到數據到來
- BIO的主要性能差在,對于每一個連接都要有一個線程來進行處理样悟,而NIO可以用很少的線程完成大量的連接處理拂募。而線程的上下文切換是非常耗費CPU資源的。
NIO的原理
由前面的描述可以推得NIO的寫法是(以服務端為例):
- 先創(chuàng)建一個Channel 窟她,這個Channel是用來作為服務器傳輸數據的通道陈症,并將這個Channel綁定到某個端口上,監(jiān)聽這個端口震糖。
2.打開Selector录肯,獲取得到一個Selector,然后將Channel注冊到這個這個Selector上吊说,同時注冊相關事件(讀寫或連接等事件)
3.之后Selector會一直阻塞到有注冊事件的到來论咏,當事件到來時优炬,如果發(fā)現是連接事件,則將會向Selector中再次注冊這個Channel厅贪。讀寫事件交由其他部分來完成
由以上的分析可以看出蠢护,NIO中比較關鍵的就是Selector,Channel养涮,而這些實例的初始化都需要通過SelectorProvider類實現
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
SelectorProvider是什么
直接查閱jdk doc 上面是這么說的
A selector provider is a concrete subclass of this class that has a zero-argument constructor and implements the abstract methods specified below. A given invocation of the Java virtual machine maintains a single system-wide default provider instance, which is returned by the provider method. The first invocation of that method will locate the default provider as specified below.