这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战
Java NIO 选择器
通道处于就绪状态后,就可以在缓冲区之间传送数据。可以采用非阻塞模式来检查通道是否就绪,但非阻塞模式还会做别的任务,当有多个通道同时存在时,很难将检查通道是否就绪与其他任务剥离开来,或者说是这样做很复杂,即使完成了这样的功能,但每检查一次通道的就绪状态,就至少有一次系统调用,代价十分昂贵。当你轮询每个通道的就绪状态时,刚被检查的一个处于未就绪状态的通道,突然处于就绪状态,在下一次轮询之前是不会被察觉的。操作系统拥有这种检查就绪状态并通知就绪的能力,因此要充分利用操作系统提供的服务。在 Java 中,Selector 类就提供了这种抽象,拥有询问通道是否已经准备好执行每个 I/O 操作的能力,所以可以利用选择器来很好地解决以上问题。
选择器(Selector)
选择器类管理这一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。
可选择通道(SelectableChannel)
这个抽象类提供了通道的可选择性所需要的公共方法。FileChannel 对象不是可选择的,因为它们没有继承 SelectableChannel。所有的 socket 通道都是可选择的,包括从管道(Pipe)对象中获取的通道。SelectableChannel 可以被注册到 Selector 对象上,一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。
选择键(SelectionKey)
选择键封装了通道与选择器的注册关系。选择键对象被SelectableChannel.register 返回并提供一个表示这种注册关系的标记。通道在被注册到一个选择器上之前,必须先设置为非阻塞模式(通过调用configureBlocking(false))
调用可选择通道的 register() 方法会将它注册到一个选择器上。如果试图注册一个处于阻塞状态的通道,register() 将抛出未检查的 illegalBlockingModeException 异常。此外,通道一旦被注册,就不能回到阻塞状态。试图这么做的话,将在调用 configureBlocking() 方法时将抛出illegalBlockingModeException 异常。并且,试图注册一个已经关闭的 SelectableChannel 实例的话,也将抛出 losedChannelException 异常。
键的 interest (感兴趣的操作)集合和 ready(已经准备好的操作)集合是和特定的通道相关的。每个通道的实现,将定义为它自己的选择键类。在register() 方法中可以构造它将它传递给所提供的选择器对象。
如果我们要使用非阻塞 I/O 编写服务器处理程序,大致的步骤如下:
1、向 Selector 对象注册感兴趣的事件
2、从 Selector 中获取感兴趣的事件
3、根据不同的事件进行相应的处理
在 SelectionKey 中,用静态常量定义了四种 I/O 操作:IP_READ 1、OP_WRITE 4、OP_CONNECT 8、OP_ACCEPT 16,这四个值任何2、3、4个相加结构都不相同,因此可以用 validOps() 方法返回值确定 SelectableChannel 支持的操作。
当通道关闭时,所有相关的键会自动取消。当选择器关闭时,所有被注册到该选择器的通道都将被注销,并且相关的键将立即被无效化(取消)。一旦键被无效化,调用它的与选择相关的方法就将抛出 CancelledKeyException。
通过 keys() 方法已注册键的集合,集合可能是空的。这个已注册的键的集合不是可以直接修改的,试图这么做的话将java.lang.UnsupportedOperationException。
已选择的键的集合(Selected key set)是已注册的键的集合的子集,这个集合的每个成员都是相关通道被选择器判断已经准备好的。这个集合通过 selected keys() 方法返回(可能是空的)。
已取消的键的集合(Cancelled key set)是已注册的键的集合的子集,这个集合包含了 cancel() 方法被调用过的键(这个键已经被无效化),但是它们还没有被注销。这个集合是选择器对象的私有成员,因而无法直接访问。
以下三种方式可以唤醒在 select() 方法中睡眠的线程:
a、调用 Selector 对象的 wakeup() 方法将使得选择器上的第一个还没有返回的选择操作立即返回。如果当前没有在进行中选择,那么下一次对 select() 方法的一种形式的调用将立即返回。后续的选择操作将正常进行。在选择操作之间多次调用 wakeup() 方法与调用它一次没有什么不同。
b、如果选择器的 close() 方法被调用,那么任何一个在选择操作中阻塞的线程都将被唤醒,就像 wakeup() 方法被调用了一样。但是与选择器相关的通道将被注销,键将被取消。
c、如果睡眠中的线程的 interrupt() 方法被调用,它的返回状态将被设置。如果被唤醒的线程之后将试图在通道上执行 I/O 操作,通道将立即关闭,然后线程将捕捉到一个异常。Selector 对象将捕捉 InterruptedException 异常并调用 wakeup() 方法。