Java NIO — 选择器(Selector)

Selector

Java NIO 的实现关键在于多路复用 I/O 技术,而多路复用的核心在于通过 Selector 以轮询的方式查找注册在其上的 Channel,当发现某个或者多个 Channel 处于就绪状态后,从阻塞状态返回就绪的 Channel 的选择键集合,进行后续I/O 操作。

Selector 一般翻译为选择器,也可以翻译为多路复用器。Selector 需要结合通道 Channel 一起使用,关于 Channel 可以参考之前的一篇文章《Java NIO 通道》

Selector 的意义在于只通过一个线程就可以管理成千上万个 I/O 请求, 相比使用多个线程,避免了线程上下文切换带来的开销(随着 CPU 和操作系统的发展,多线程造成的开销正变得越来越小)。


Selector 使用方式

Selector的使用步骤主要分为以下几步:
  • 创建 Selector
  • 向 Selector 注册通道,一个 Selector 可以注册多个通道
  • 通过 Selector 选择就绪的通道

注意:Selector 只能与非阻塞模式下的通道一起使用(即需要实现 SelectableChannel 接口),否则会抛出 IllegalBlockingModeException 异常,也意味着文件通道 FileChannel 无法使用 Selector。

下面分步进行讲解:

  • 通过 open() 方法创建 Selector

Selector selector = Selector.open();

  • 创建一个通道,以 ServerSockeetChannel 为例

ServerSocketChannel channel = ServerSocketChannel.open();

  • 将通道设置为非阻塞模式

channel.configureBlocking(false);

  • 通过 register() 方法注册通道

channel.register(selector, SelectionKey.OP_ACCEPT);

  • 通过 select() 方法从多个通道中以轮询的方式选择已经准备就绪的通道。准备就绪的意思是根据之前 register() 方法中设置的兴趣,将可以进行对应操作的通道选择出来

selector.select();

  • 通过 Selector 的 selectedKeys() 方法获得已选择键集(selected-key set)

Set key = selector.selectedKeys();

  • 通过 Iterator 迭代器依次获取 key 中的 SelectionKey 对象,并通过 SelectionKey 中的判断方法执行对应的操作
Iterator<SelectionKey> iterator = key.iterator();
while (iterator.hasNext()) {
    SelectionKey selectionKey = iterator.next();
    if (selectionKey.isAcceptable()) {
        //TODO
    }
}

register() 方法

register() 方法返回 SelectionKey 对象,其定义在 SelectableChannel 抽象类中

public final SelectionKey register(Selector sel, int ops) 
                               throws ClosedChannelException {
    return register(sel, ops, null);
}

register(sel, ops, null) 方法为抽象方法,具体实现在 AbstractSelectableChannel 抽象类中,有兴趣的可以研究一下源码,在此不再赘述。

register 方法中的三个参数含义如下:

  • Selector sel:通道注册的选择器
  • int ops:interest集合,表示通过 Selector 监听 Channel 时对什么事件感兴趣
  • Object att:这是一个可选参数,在注册通道时可以附加一个对象,用于之后便于识别某个通道

interest 集合有如下四种操作类型:

注意:通道一般并不会同时支持这四种操作类型,我们可以通过 validOps() 方法获取通道支持的类型。例如,ServerSocketChannel 只支持 SelectionKey.OP_CONNECT操作。


select() 方法

select() 方法有两种重载方法:

  • int select():选择已准备就绪的通道,返回值表示自上一次选择后有多少新增通道准备就绪;当没有通道准备就绪时,会一直阻塞下去,直到至少一个通道被选择、该选择器的 wakeup() 方法被调用或当前线程被中断时。select() 方法实际上调用了 select(0L) 方法返回
  • int select(long timeout):选择已准备就绪的通道;当没有通道准备就绪时,会一直阻塞下去,直到至少一个通道被选择、该选择器的 wakeup() 方法被调用、当前线程被中断或给定时间到期时返回

此外,还可以选择 selectNow() 方法,该方法为非阻塞方法,无论有无通道就绪都会立即返回。如果自前一次 select 操作后没有新的通道准备就绪,则会立即返回 0。


SelectionKey

SelectionKey 中有如下几种判断方法,与操作类型相对应:

  • boolean isReadable():是否可读,是返回 true
  • boolean isWritable():是否可写,是返回 true
  • boolean isConnectable():是否可连接,是返回 true
  • boolean isAcceptable():是否可接收,是返回 true

注意:selectedKeys() 获得的是已就绪的通道对应的 SelectionKey。如果想获得该选择器上所有通道对应的 SelectionKey,可以通过 keys() 方法获取。

参考资料:

猜你喜欢

转载自blog.csdn.net/weixin_43320847/article/details/83240927