文章目录
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关闭