文章目錄
NIO簡介
NIO,New IO(官方),Non-blocking IO(非官方),是 JDK1.4 中引入的一種新的 IO 標準,是一種同步非阻塞 IO。NIO 是以塊爲單位進行數據處理的,當然塊的大小是程序員自己指定的。其相對於 BIO 的以字節/字符爲單位所進行的阻塞式處理方式,大大提高了讀寫效率與併發度。
- BIO:Blocking IO,同步阻塞 IO
- NIO:Non-blocking IO,同步非阻塞 IO JDK1.4
- AIO:異步非阻塞 IO,也稱爲 NIO2.0 JDK1.7
爲什麼NIO適合高併發的場景
首先看下面這張BIO的客戶端與服務器線程之間的示意圖
有圖可知,每一個客戶端與服務器建立鏈接就會產生一個線程,而線程資源是非常寶貴的,而且是有上線的,所以就造成了BIO併發處理的瓶頸。而NIO爲了解決這一點使用了下面的模型
NIO中Channel與線程之間是多對一的關係。而哪一個Channel使用線程是通過selector這一多路複用器來決定的。當一個Channel準備就緒時就通知線程來處理該通道的請求。由於多個Channel對應一個線程,所以NIO是可以處理高併發的。
JAVA NIO中重要的API
java.nio.channels.spi.SelectorProvider
該類主要負責提供選擇器以及可以選擇的Channel。
該類通過provider()維護了一個全局且唯一的一個SelectorProvider實例,而且該類的所有方法都是線程安全的。
/**
* Returns the system-wide default selector provider for this invocation of
* the Java virtual machine.
*
* <p> The first invocation of this method locates the default provider
* object as follows: </p>
*
* <ol>
*
* <li><p> If the system property
* <tt>java.nio.channels.spi.SelectorProvider</tt> is defined then it is
* taken to be the fully-qualified name of a concrete provider class.
* The class is loaded and instantiated; if this process fails then an
* unspecified error is thrown. </p></li>
*
* <li><p> If a provider class has been installed in a jar file that is
* visible to the system class loader, and that jar file contains a
* provider-configuration file named
* <tt>java.nio.channels.spi.SelectorProvider</tt> in the resource
* directory <tt>META-INF/services</tt>, then the first class name
* specified in that file is taken. The class is loaded and
* instantiated; if this process fails then an unspecified error is
* thrown. </p></li>
*
* <li><p> Finally, if no provider has been specified by any of the above
* means then the system-default provider class is instantiated and the
* result is returned. </p></li>
*
* </ol>
*
* <p> Subsequent invocations of this method return the provider that was
* returned by the first invocation. </p>
*
* @return The system-wide default selector provider
*/
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;
}
});
}
}
後面不管是取得通道還是選擇器都是通過這個全局唯一的provider實例來獲取的。
/**
* Opens a selector.
*
* <p> The new selector is created by invoking the {@link
* java.nio.channels.spi.SelectorProvider#openSelector openSelector} method
* of the system-wide default {@link
* java.nio.channels.spi.SelectorProvider} object. </p>
*
* @return A new selector
*
* @throws IOException
* If an I/O error occurs
*/
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
/**
* Opens a server-socket channel.
*
* <p> The new channel is created by invoking the {@link
* java.nio.channels.spi.SelectorProvider#openServerSocketChannel
* openServerSocketChannel} method of the system-wide default {@link
* java.nio.channels.spi.SelectorProvider} object.
*
* <p> The new channel's socket is initially unbound; it must be bound to a
* specific address via one of its socket's {@link
* java.net.ServerSocket#bind(SocketAddress) bind} methods before
* connections can be accepted. </p>
*
* @return A new socket channel
*
* @throws IOException
* If an I/O error occurs
*/
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
java.nio.channels.Selector
- 該類在NIO中充當的是一個多路複用器的角色。
- 這個類創建有兩種方法,一種是Selector的open()方法,一種是SelectorProvider的openSelector()方法,不過第一種方式本質上也是調用的SelectorProvider的openSelector()方法。
- 選擇器被open之後會一直處於open狀態,直到調用了Selector的close()方法。
- 可選擇通道是通過SelectionKey的方式向選擇器的註冊的。一個通道對應一個SelectionKey。
- Selector一共維護了三個SelectionKey的集合,分別爲所有的Key的集合(key set),所有的準備就緒的Key的集合(selected-key set),處理完畢的待從selector中刪除的key的集合(cancelled-key set)。在下一次選擇時會把這裏面的Key從所有Key集合中刪除掉。
- key set元素的添加是在Channel註冊到Selector時,刪除是在每次選擇操作時將包含在cancelled-key set中的元素刪除。注意該集合本身是不會直接進行修改的
- selected-key set中的元素添加是在Selector進行選擇操作時添加的,並且永遠不會自動移除,需要你手動調用set集合的remove方法或者Set集合Iterator的remove方法進行移除,也就是說在進行業務處理之後你要手動調用remove方法來避免重複的操作。
- cancelled-key set中的元素在調用SelectionKey的cancle方法時添加,在下一次選擇操作時從該集合以及 key set對應的元素進行刪除。
- Selector的選擇操作select()採用了poll輪詢的方式挨個查詢是否有準備就緒的Channel期間執行線程是阻塞狀態,當然你可以指定阻塞超時時間,來結束阻塞狀態。該方法返回值有可能爲0,代表該階段沒有準備就緒的通道。選擇操作會對三個元素集合產生影響。會刪除cancelled-key set中的元素以及key set中對應的元素,會將準備就緒的通道放入selected-key set中。
java.nio.channels.SelectionKey
- 每一個Channel註冊至Selector中都會產生一個SelectionKey。
- 一個SelectionKey的取消有三種方式,SelectionKey的取消操作,通道的關閉,以及Selector關閉。
- 一個SelectionKey包含一個或者多個感興趣的操作,即Selector輪詢時監聽的事件。包括OP_READ(有消息到達可以被讀取)。OP_WRITE(可以被寫入)。OP_CONNECT(套接字通道已經準備好完成它的連接)。OP_ACCEPT(有通道進行接入)
NIO服務端處理消息的流程
- 打開一個服務端的通道,並選擇監聽的端口。
- 打開一個Selecor
- 將通道註冊到Selector中,並選擇需要監聽的事件
- Selector以輪詢的方式查找是否有準備就緒的通道,並將該通道的SelectionKey放入selected-key set中。
- 將處理過的通道的key從selected-key set中移除
- 繼續第4步直到Channel或者Selector關閉