选择器提供选择执行已经就绪的任务的能力,这使得多元 I/O 成为可能。就像在第一章中描述的那样,就绪选择和多元执行使得单线程能够有效率地同时管理多个 I/O 通道(Channels)。C/C++代码的工具箱中,许多年前就已经有 select()和 poll()这两个POSIX(可移植性操作系统接口)系统调用可供使用了。许过操作系统也提供相似的功能,但对Java 程序员来说,就绪选择功能直到 JDK 1.4 才成为可行的方案。
从最基础的层面来看,选择器提供了询问通道是否已经准备好执行每个I/0操作的能力。例如,我们需要了解一个SocketChannel对象是否还有更多的字节需要读取,或者我们需要知道ServerSocketChannel是否有需要准备接受的连接。在与SelectableChannel联合使用时,选择器提供了这种服务,但这里面有更多的事情需要去了解。就绪选择的真正价值在于潜在的大量的通道可以同时进行就绪状态的检查。调用者可以轻松地决定多个通道中的哪一个准备好要运行。有两种方式可以选择:被激发的线程可以处于休眠状态,直到一个或者多个注册到选择器的通道就绪,或者它也可以周期性地轮询选择器,看看从上次检查之后,是否有通道处于就绪状态。 乍一看,好像只要非阻塞模式就可以模拟就绪检查功能,但实际上还不够。非阻塞模式同时还会执行您请求的任务,或指出它无法执行这项任务。这与检查它是否能够执行某种类型的操作是不同的。举个例子,如果您试图执行非阻塞操作,并且也执行成功了,您将不仅仅发现read( )是可以执行的,同时您也已经读入了一些数据。就下来您就需要处理这些数据了。效率上的要求需要将检查就绪的代码和处理数据的代码分离开来,至少这么做会很复杂。即使简单地询问每个通道是否已经就绪的方法是可行的,代码需要遍历每一个候选的通道并按顺序进行检查的时候,仍然是有问题的。这会使得在检查每个通道是否就绪时都至少进行一次系统调用,这种代价是十分昂贵的,但是主要的问题是,这种检查不是原子性的。列表中的一个通道都有可能在它被检查之后就绪,但直到下一次轮询为止,您并不会觉察到这种情况。最糟糕的是,除了不断地遍历列表之外将别无选择。因为无法在某个您感兴趣的通道就绪时得到通知。
真正的就绪选择必须由操作系统来做。操作系统的一项最重要的功能就是处理I/O请求并通知各个线程它们的数据已经准备好了。选择器类提供了这种抽象,使得Java代码能够以可移植的方式,请求底层的操作系统提供就绪选择服务。
执行就绪选择的三个核心类:可选择通道(SelectableChannel) ,选择器(Selector),选择键(SelectionKey)
可选择通道(SelectableChannel):
核心方法:
configureBlocking(),isBlocking(),blockingLock()三个方法来配置并检查通道的阻塞模式,通道在被注册到一个选择器上之前,必须先设置为非阻塞模式(通过调用configureBlocking(false))。
调用可选择通道的register( )方法会将它注册到一个选择器上。如果您试图注册一个处于阻塞状态的通道,register( )将抛出未检查的IllegalBlockingModeException异常。
异常检查代码如下:
if (!isOpen()) throw new ClosedChannelException(); if ((ops & ~validOps()) != 0) throw new IllegalArgumentException(); if (blocking) throw new IllegalBlockingModeException();
此外,通道一旦被注册,就不能回到阻塞状态。试图这么做的话,将在调用configureBlocking( )方法时将抛出IllegalBlockingModeException异常,configureBlocking代码如下:
if (block && haveValidKeys()) throw new IllegalBlockingModeException();
原文地址:https://www.cnblogs.com/heapStark/p/8178314.html