Java NIO 的实现关键在于多路复用 I/O 技术,而多路复用的核心在于通过 Selector 以轮询的方式查找注册在其上的 Channel,当发现某个或者多个 Channel 处于就绪状态后,从阻塞状态返回就绪的 Channel 的选择键集合,进行后续I/O 操作。
Selector 一般翻译为选择器,也可以翻译为多路复用器。Selector 需要结合通道 Channel 一起使用,关于 Channel 可以参考之前的一篇文章《Java NIO 通道》。
Selector 的意义在于只通过一个线程就可以管理成千上万个 I/O 请求, 相比使用多个线程,避免了线程上下文切换带来的开销(随着 CPU 和操作系统的发展,多线程造成的开销正变得越来越小)。
注意:Selector 只能与非阻塞模式下的通道一起使用(即需要实现 SelectableChannel 接口),否则会抛出 IllegalBlockingModeException 异常,也意味着文件通道 FileChannel 无法使用 Selector。
下面分步进行讲解:
Selector selector = Selector.open();
ServerSocketChannel channel = ServerSocketChannel.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);
selector.select();
Set key = selector.selectedKeys();
Iterator<SelectionKey> iterator = key.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); if (selectionKey.isAcceptable()) { //TODO } }
register() 方法返回 SelectionKey 对象,其定义在 SelectableChannel 抽象类中
public final SelectionKey register(Selector sel, int ops) throws ClosedChannelException { return register(sel, ops, null); }
register(sel, ops, null) 方法为抽象方法,具体实现在 AbstractSelectableChannel 抽象类中,有兴趣的可以研究一下源码,在此不再赘述。
register 方法中的三个参数含义如下:
interest 集合有如下四种操作类型:
注意:通道一般并不会同时支持这四种操作类型,我们可以通过 validOps() 方法获取通道支持的类型。例如,ServerSocketChannel 只支持 SelectionKey.OP_CONNECT操作。
select() 方法有两种重载方法:
此外,还可以选择 selectNow() 方法,该方法为非阻塞方法,无论有无通道就绪都会立即返回。如果自前一次 select 操作后没有新的通道准备就绪,则会立即返回 0。
SelectionKey 中有如下几种判断方法,与操作类型相对应:
注意:selectedKeys() 获得的是已就绪的通道对应的 SelectionKey。如果想获得该选择器上所有通道对应的 SelectionKey,可以通过 keys() 方法获取。
参考资料: