有关于Selector的使用,我们在之前的示例中就已经用到过了。当然,基本都是最简单的使用。而有关于Selector的其他API、SelectionKey的API,我们都没有详细介绍过。故单独列一篇,来说明下其使用。
// 创建,创建比较简单,就是一句话,实际后面做了很多工作。
Selector selector = Selector.open();
// Selector.open
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
// SelectorProvider.provider()
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
// 加载java.nio.channels.spi.SelectorProvider系统参数配置对应的Provider
if (loadProviderFromProperty())
return provider;
// SPI方式加载SelectorProvider实现类
if (loadProviderAsService())
return provider;
// 以上两种都没有,则返回默认provider。
// 笔者Windows系统下直接返回WindowsSelectorProvider
// 最终Selector为WindowsSelectorImpl
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
// 获取哪些已经准备好的channel数量。非阻塞,方法会立即返回
public abstract int selectNow() throws IOException;
// 同selectNow,会一直阻塞到有准备好的channel事件为止
public abstract int select() throws IOException;
// 同select(),会阻塞最多timeout毫秒后返回
public abstract int select(long timeout)
我们可以在使用中选择合适的select方法,避免长时间的线程阻塞。
Selector提供wakeup方法来唤醒阻塞在1.1中select方法中的线程。
/**
* Causes the first selection operation that has not yet returned to return
* immediately.
*
* <p> If another thread is currently blocked in an invocation of the
* {@link #select()} or {@link #select(long)} methods then that invocation
* will return immediately. If no selection operation is currently in
* progress then the next invocation of one of these methods will return
* immediately unless the {@link #selectNow()} method is invoked in the
* meantime. In any case the value returned by that invocation may be
* non-zero. Subsequent invocations of the {@link #select()} or {@link
* #select(long)} methods will block as usual unless this method is invoked
* again in the meantime.
*
* <p> Invoking this method more than once between two successive selection
* operations has the same effect as invoking it just once. </p>
*
* @return This selector
*/
public abstract Selector wakeup();
注解真的很全了。
如果当前线程阻塞在select方法上,则立即返回;
如果当前Selector没有阻塞在select方法上,则本次wakeup调用会在下一次select方法阻塞时生效;
public abstract void close() throws IOException;
public abstract boolean isOpen();
当我们不再使用Selector时,需要调用close方法来释放掉其它占用的资源,并将所有相关的选择键设置为无效。
被close后的Selector则不能再使用。
同时提供了isOpen方法来检测Selector是否开启。
// Returns this selector's key set.
public abstract Set<SelectionKey> keys();
// Returns this selector's selected-key set.
public abstract Set<SelectionKey> selectedKeys();
keys方法返回的是目前注册到Selector上的所有channel以及对应事件;
selectedKeys方法返回的是目前注册到Selector上的所有channel活跃的事件。返回的结果集的每个成员都被判定是已经准备好了。
故,我们之前一直使用的就是selectedKeys方法。
那么SelectionKey是什么呢?接着看。
// A token representing the registration of
// a {@link SelectableChannel} with a {@link Selector}.
public abstract class SelectionKey {
// 代表我们关注的4种事件
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
// SelectionKey本质上就是channel与selector的关联关系
public abstract SelectableChannel channel();
public abstract Selector selector();
// channel注册到selector上时,关注的事件
public abstract int interestOps();
// 当前channel已经准备好的事件
public abstract int readyOps();
// 附加信息。我们在channel.register(selector,ops,att)方法时,最后一个参数
// 即指定了本次注册的附加信息
public final Object attach(Object ob);
public final Object attachment();
}
通过上面的注释,我们可以了解到,SelectionKey本质上就是channel注册到selector上后,用于绑定两者关系的一个类。对于channel关注的事件,添加的附件信息在这个类均有所体现。
最后再来说下SelectableChannel,之前提到的SocketChannel和ServerSocketChannel都是SelectableChannel的实现类。
public abstract class SelectableChannel
extends AbstractInterruptibleChannel
implements Channel
{
// 最重要的两个方法,用于注册channel到selector上,区别就是有没有attachment
public abstract SelectionKey register(Selector sel, int ops, Object att)
throws ClosedChannelException;
public final SelectionKey register(Selector sel, int ops)
throws ClosedChannelException;
// 检查当前channel是否注册到任何一个selector上
public abstract boolean isRegistered();
// 当前channel可注册的有效的事件
public abstract int validOps();
// 阻塞状态,之前的SocketChannel文章中已经详细说过
public abstract SelectableChannel configureBlocking(boolean block)
throws IOException;
// 检查channel阻塞状态
public abstract boolean isBlocking();
}
注意:
1. 一个channel可以注册到多个Selector上
2.每个channel可注册的有效事件不同,如下
// SocketChannel
public final int validOps() {
return (SelectionKey.OP_READ
| SelectionKey.OP_WRITE
| SelectionKey.OP_CONNECT);
}
//ServerSocketChannel
public final int validOps() {
return SelectionKey.OP_ACCEPT;
}
笔者对于在学习NIO这一阶段时间的感受就是:多去尝试,多看注释。
NIO属于笔者阶段性总结博客的第一站,后续还有更多系列博客要出来。
为什么选择NIO作为第一站呢?因为众多高精尖技术的底层就是NIO。所以弄明白NIO的一系列使用是很有必要的。
之前也知道BIO和NIO的大致写法,基本都是从网上看示例,直接拷贝。但是真让自己来写的话,又是漏洞百出,究其原因,就是对NIO的使用和其原理不甚了解。
通过这一次的总结性输出,对NIO的理解也算是上了一个层次。
与诸君共勉之!