通道处于就绪状态后,就可以在缓冲区之间传递数据,可以采用非阻塞模式来检查通道是否就绪,但非阻塞模式还会做别的任务,当有多个通道同时存在时,很难将检查通道是否就绪与其他任务剥离开来,或者说是这样做很复杂,即使完成了这样的功能,但每检查一次通道的就绪状态,就至少有一次系统调用,代价十分昂贵.
当你轮询每个通道的就绪状态时,刚被检查的一个处于未就绪的状态的通道,突然处于就绪状态,在下一次轮询之前是不会被察觉的.
操作系统拥有这种检查就绪状态并通知就绪的能力,因此要充分利用操作系统提供的服务,在Java类中,Selector类提供了这种抽象的能力,拥有询问通道是否已经准备好执行每个I/O操作的能力.
Selector的作用就是用来轮询每个注册的Channel,一旦发现Channel有注册的事件发生,便获取事件然后进行处理。
以前传统socket编程时,accept方法会一直阻塞,直到有客户端请求的到来,并返回socket进行相应的处理。整个过程是就像上面的例子那样,直到水壶烧开了(响应回去了)才能去处理下一个请求.当然我们也可以用线程池的模式.
NIO则为我们提供了更好的解决方案,Selector选择器能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。并且是按顺序处理,基于通道(Channel)和缓冲区(Buffer)来传输和保存数据。
与Selector有关的一个关键类是SelectionKey,一个SelectionKey表示一个到达的事件,这2个类构成了服务端处理业务的关键逻辑。
选择键(SelectionKey)
是一个抽象类,表示selectableChannel在Selector中注册的标识.每个Channel向Selector注册时,都将会创建一个selectionKey.
选择键封装了通道与选择器的注册关系,选择键对象被SelectableChannel.register返回并提供了一个表示这种注册关系的标记,通道在被注册到一个选择器上之前,必须先设置为非阻塞模式.(通过调用configureBlocking(false)).