Selector提供选择执行已经就绪的任务的能力,使得多元 I/O 成为可能,就绪选择和多元执行使得单线程可以有效率地同时管理多个 I/O channel。java
C/C++许多年前就已经有 select()和 poll()这两个POSIX(可移植性操做系统接口)系统调用可供使用。许多os也提供类似的功能,但对Java 程序员来讲,就绪选择功能直到 JDK 1.4 才成为可行方案。程序员
获取到SocketChannel
后,直接包装成一个任务,提交给线程池。 引入Selector后, 须要将以前建立的一或多个可选择的Channel注册到Selector对象,一个键(SelectionKey
)将会被返回。 SelectionKey
会记住你关心的Channel,也会追踪对应的Channel是否已就绪。 安全
每一个Channel在注册到Selector时,都有一个感兴趣的操做。服务器
新的selector是经过调用系统默认的SelectorProvider对象的openSelector方法而建立的。 markdown
Selector.open()
不是单例模式的,每次调用该静态方法,会返回新的Selector实例。网络
可经过 Selector 被多路复用的channel。多线程
为了与一个 selector 被使用,这个类的一个实例必须首先经由register方法。 该方法返回一个新SelectionKey表示与所述选择channel的注册对象。并发
一旦与一个Selector注册,直到它的channel残存部分注销。 这包括被分配到由选择的channel任何资源解除分配。socket
channel不能被直接注销; 相反,表明其注册的键必须取消。 取消键请求信道选择器的下一个选择操做期间注销。 一键能够明确地经过调用其取消cancel方法。 当channel被关闭时,全部的channel的key被隐式关闭,不管是经过调用其close方法或经过中断一个线程阻塞于所述channel的I / O操做。 若是选择器自己被关闭,则通道将被注销,以及表示其注册的键将被无效,而无需进一步的延迟。ide
虽说一个通道能够被注册到多个选择器上,但对每一个选择器而言只能被注册一次
不管是否channel与一个或多个选择可能经过调用来肯定注册isRegistered方法。 可选择的channel是由多个并发线程安全使用。
可选择的信道或者是在阻断模式或非阻塞模式。 在阻塞模式中,每个I / O操做在所述信道调用将阻塞,直到它完成。 在非阻塞模式的I / O操做不会阻塞,而且能够传送比被要求或全部可能没有字节更少的字节。 可选择信道的阻塞模式可经过调用其来肯定isBlocking方法。 新建立的可选择通道老是处于阻塞模式。 非阻塞模式是在与基于选择复用相结合最有用的。 信道必须被放置到非阻塞模式与一个选择器注册以前,而且能够不被返回到直到它已被注销阻塞模式。
Selector(选择器)是Java NIO中可以检测一到多个NIO通道,并可以知晓通道是否为诸如读写事件作好准备的组件。这样,一个单独的线程能够管理多个channel,从而管理多个网络链接。
Selector容许单线程处理多个Channel。使用Selector,首先得向Selector注册 Channel,而后调用它的select()。该方法会一直阻塞,直到某个注册的Channel有事件就绪。一旦这个方法返回,线程就能够处理这些事件,事件的例子如新链接 进来,数据接收等。
单线程处理多Channel的好处: 只需更少线程处理channel。事实上就是能够只用一个线程处理全部Channel。对于os,线程间上下文切换开销很大且每一个线程都要占用系统资源。所以,使用线程越少越好。
但现代os和CPU在多任务方面表现的愈来愈好,多线程开销变得愈来愈小。实际上,若CPU是多核的,不使用多任务可能就是在浪费CPU性能。使用Selector可以处理多个通道就足够了。
经过调用Selector.open()方法建立一个Selector,以下:
为了将Channel和Selector搭配使用,必须将channel注册到selector。 经过SelectableChannel.register()
:
// 必须是非阻塞模式
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
复制代码
configureBlocking()
用于设置通道的阻塞模式,该方法会调用implConfigureBlocking
implConfigureBlocking
会更改阻塞模式为新传入的值,默认为true,传入false,那么该通道将调整为非阻塞。而NIO最大优点就是非阻塞模型,因此通常都须要设置SocketChannel.configureBlocking(false)
。 能够经过调用isBlocking()
判断某个socket通道当前处于何种模式。
与Selector一块儿使用时,Channel必须处于非阻塞模式,因此不能将FileChannel和Selector一块儿使用,由于FileChannel不能切换到非阻塞模式。而socketChannel均可以。
注意register()方法的第二个参数。这是一个“感兴趣的事件集合”,意思是在经过Selector监听Channel时,对什么事件感兴趣。可监听四种不一样类型事件:
一个有数据可读的通道能够说是“读就绪”。
等待写数据的通道能够说是“写就绪”。
通道触发了一个事件意思是该事件已经就绪。因此,某个channel成功链接到另外一个服务器称为“链接就绪”。
一个server socket channel准备好接收新进入的链接称为“接收就绪”。
这四种事件用SelectionKey的四个常量来表示:
若对不止一种事件感兴趣,那么能够用“|”操做符将常量链接:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
复制代码
封装了特定的channel与特定的Selector的注册关系。
SelectionKey对象被SelectableChannel.register(Selector sel, int ops)返回并提供一个表示这种注册关系的标记。
SelectionKey包含两个比特集(以整数形式编码):该注册关系所关心的channel操做及channel已就绪的操做。
interestOps(int) 将此key的interest设置为给定值。 能够随时调用此方法。它是否阻塞以及持续多久取决于实现。
向Selector注册Channel时,register()方法会返回一个SelectionKey对象,包含了一些你感兴趣的属性:
interest集合
interest集合是你所选择的感兴趣的事件集合。能够经过SelectionKey读写interest集合,像这样:
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
复制代码
“位与”interest 集合和给SelectionKey常量,能够肯定某事件是否在interest 集合。
通道已经准备就绪的操做的集合。在一次选择(Selection)以后,你会首先访问这个ready set。能够这样访问ready集合:
可用像检测interest集合那样检测channel中什么事件或操做已就绪。 也可以使用如下四个方法,它们都会返回一个布尔类型:
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
复制代码
从SelectionKey访问Channel和Selector很简单。以下:
可将一个对象或更多信息附到SelectionKe,就能方便识别通道。 例如,能够附加与通道一块儿使用的Buffer,或是包含汇集数据的某个对象。 使用方法以下:
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
复制代码
一个被附加的对象可能稍后就会被attachment获取到。 只有一个对象能够在一个时间被附接; 调用此方法使任何先前的附接被丢弃。 当前附接能够经过附加空被丢弃。
还可用register()向Selector注册Channel的时候附加对象。如:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
复制代码
一旦向Selector注册了一或多通道,就可调用重载的select()。这些方法返回你所感兴趣的事件(如链接、接受、读或写)已经准备就绪的那些通道。即若是你对“读就绪”通道感兴趣,select()方法会返回读事件已经就绪的那些通道。
阻塞,直到至少有一个channel在你注册的事件上就绪
和select()同样,只是规定了最长会阻塞timeout毫秒(参数)。
不会阻塞,无论什么channel就绪都马上返回(此方法执行非阻塞的选择操做。若自从上一次选择操做后,没有channel可选择,则此方法直接返回0)。
select()系列方法返回的int值表示有多少channel已就绪,即自上次调用select()方法后有多少channel变成就绪状态。 若调用select()方法,由于有一个channel变成就绪状态,返回了1,若再次调用select()方法,若是另外一个通道就绪了,它会再次返回1。 若对第一个就绪的channel没有作任何操做,如今就有两个已就绪channel。可是在每次select()方法调用之间,只有一个channel就绪了。
一旦调用select()方法,而且返回值代表有一个或更多个通道就绪了,而后能够经过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道:
Set selectedKeys = selector.selectedKeys();
复制代码
当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象表明了注册到该Selector的Channel。可经过SelectionKey的selectedKeySet()方法访问这些对象。
可遍历该selectedKeys访问就绪的Channel:
Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
复制代码
这个循环遍历已选择键集中的每一个键,并检测各个键所对应的通道的就绪事件。
注意每次迭代末尾调用keyIterator.remove()
。Selector不会本身从selectedKeys中移除SelectionKey实例。必须在处理完通道时本身移除。下次该通道变成就绪时,Selector会再次将其放入selectedKeys。
SelectionKey.channel()方法返回的通道须要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。
某个线程调用 select() 后阻塞了,即便没有channel已就绪,也有办法让其从select()返回。 只需经过其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()。阻塞在select()方法上的线程会立马返回。
如有其它线程调用了wakeup(),但当前没有线程阻塞在select(),下个调用select()方法的线程会当即“醒来(wake up)”。
用完Selector后调用其close()会关闭该Selector,且使注册到该Selector上的全部SelectionKey实例无效。但channel自己并不会关闭。
打开一个Selector,将一个channel注册到这个Selector,而后持续监控这个Selector的四种事件(接受,链接,读,写)是否就绪。
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannels = selector.select();
if(readyChannels == 0) continue;
Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
}
复制代码