Selector 选择器作为java NIO中三大重要的组件之一。它的作用能够检查一个或多个NIO通道channel,然后确定哪些通道可以用于符合我们定义的操作,例如读写、socket连接等。也是因为选择器的存在,使得我们可以使用单个线程来管理多个通道,大大提高了资源的利用
nio中selector中的优点
图片.png
可以使用单个线程来处理多个channel来节省资源。对于操作系统而言,线程之间切换是昂贵的,并且每个线程也占用操作系统中的一些资源(存储器)。 因此,使用的线程越少越好。当然,现代操作系统和CPU在多任务处理中变得越来越好,多线程的开销也会变得越来越小。所以具体的使用还需要根据自己的实际需求和相应的硬件资源。
下面使用
Selector selector = Selector.open();
//1 通道必须处于非阻塞模式才能与选择器一起使用
channel.configureBlocking(false);
//2 下面这个方法的第二个参数是我们监听通道的兴趣事件
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
我们注册的兴趣集
已经准备就绪兴趣事件
注册的channel
注册的选择器selector
附加对象(可选)
首先先介绍一下上面几种属性
//取得兴趣事件
int interestSet = selectionKey.interestOps();
//通过&检查下面是否注册了兴趣事件
boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
就绪兴趣事件集是channel准备好的一组操作。可以通过下面获取就绪集:
int readySet = selectionKey.readyOps(); 不过这种方式需要同故宫上述兴趣事件来判断那些兴趣事件准备就绪。通常我们使用下面这种方式:
//selectionKey.isAcceptable();
//selectionKey.isConnectable();
//selectionKey.isReadable();
//selectionKey.isWritable();
Iterator<SelectionKey> 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();
}
Channel channel = selectionKey.channel(); Selector selector = selectionKey.selector();
//附加对象到selectionKey上
selectionKey.attach(theObject);
//从selectionKey种取得附加对象
Object attachedObj = selectionKey.attachment();
//可以在register()方法中使用Selector注册Channel时附加一个对象
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
在Selector中注册一个或多个通道后,可以调用selector.select()方法。 这个方法返回感兴趣的事件(连接,接受,读取或写入)已准备好的通道得数量。源码 javadoc描述:这是一个阻塞方法,直到有事件准备好了,返回准备好的事件数量。
/**
* Selects a set of keys whose corresponding channels are ready for I/O
* operations.
*
* <p> This method performs a blocking <a href="#selop">selection
* operation</a>. It returns only after at least one channel is selected,
* this selector's {@link #wakeup wakeup} method is invoked, or the current
* thread is interrupted, whichever comes first. </p>
*
* @return The number of keys, possibly zero,
* whose ready-operation sets were updated
* @throws IOException
* If an I/O error occurs
* @throws ClosedSelectorException
* If this selector is closed
*/
public abstract int select() throws IOException;
当一个线程调用select()方法时,会被阻塞到有准备事件到来,但是如果我们想释放这个线程,可以通过在另外的线程中调用selector.wakeup()方法。这里的这个选择器对象应该是同一个。
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> 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();
}
注意: 每次迭代结束时的keyIterator.remove()。 因为选择器不会自己从所选键集本身中删除SelectionKey实例。 在完成channel处理后,需要手动调用remove()删除。 当下一次channel变为“就绪”状态时,selector会再次将其添加到SelectionKey中去。
给出一个完整的例子,包含打开一个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<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> 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();
}
}