Selector选择器是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样使得一个单独的线程可以管理多个Channel,从而管理多个网络连接。
为什么使用Selector
对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的资源,所以使用Selector单独管理多个Channel
Selector的创建
Selector selector = Selector.open();
向Selector注册通道
channel.configureBlocking(false);
SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_READ);
与Selector一起使用时,通道必须处于非阻塞模式,这意味着FileChannel不能与Selector一起使用。套接字通道都可以
register()方法的第二个参数是一个“interest集合”,意思是通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件,用四种常量表示:
- SelectionKey.OP_CONNECT: 连接就绪(某个Channel成功连接到另一个服务器)
- SelectionKey.OP_ACCEPT: 接收就绪(一个ServerSocketChannel准备好接收新进入的连接)
- SelectionKey.OP_READ: 读就绪(一个有数据可读的通道)
- SelectionKey.OP_WRITE: 写就绪(等待写数据的通道)
通道触发了一个事件,就表示该事件已经就绪。如果对不止一种事件感兴趣,可以用位或操作符将常量连接:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
SelectionKey
这个对象包含了一些属性:
- interest集合
- ready集合
- Channel
- Selector
- 附加的对象(可选)
interest集合
interest集合是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合
int interestSet = selectionKey.interestOps();
boolean isAccept = (interest & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isConnect = (interest & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT;
boolean isRead = (interest & SelectionKey.OP_READ) == SelectionKey.OP_READ;
boolean isWrite = (interest & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;
用位与操作interest集合和SelectionKey常量可以判断某个确定的事件是否在interest集合中
ready集合
ready集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后,会首先访问ready set。
int readySet = selectionKey.readyOps();
可以像检测interest集合一样,检测Channel中有哪些事件或操作已经准备就绪,也可以用以下方法:
boolean isAccept = selectionKey.isAcceptable();
boolean isConnect = selectionKey.isConnectable();
boolean isRead = selectionKey.isReadable();
boolean isWrite = selectionKey.isWriteable();
Channel和Selector
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
附加对象
可以将一个对象或更多信息附加到SelectionKey上,这样能方便识别某个通道。例如,可以附加与通道一起使用的Buffer,或是包含聚集数据的某个对象
selectionKey.attach(object);
Object obj = selectionKey.attachment();
还可以用register()方法向Selector注册Channel时附加对象
SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_READ, object);
通过Selector选择通道
一旦向Selector注册了一个或多个通道,就可以调用几个重载的select()方法。这些方法会返回你所感兴趣的事件已经准备就绪的通道。
- int select()
- int select(long timeout)
- int selectNow()
select()方法会阻塞到至少有一个通道在你注册的事件上就绪了
select(long timeout)和select()一样,除了最长会阻塞timeout毫秒
selectNow()不会阻塞,不管什么通道就绪都立刻返回
select()方法返回的int值表示有多少通道已经就绪,即自从上次调用select()方法后有多少通道变成了就绪状态。如果调用一次select()方法,因为有一个通道变成了就绪状态,返回1,如果再次调用select()方法,如果另一个通道也就绪了,则还会返回1。即使,第一个就绪的通道没有做任何操作,但是每次select()方法调用之间,只有一个通道变成就绪状态
selectedKeys()
一旦调用了select()方法,且返回值表明有一个或多个通道就绪了,然后可以调用Selector的selectedKeys()方法,访问“已选择键集“(selected key set)中的就绪通道
Set selectedKeys = selector.selectedKeys();
当向Selector注册Channel时,Channel.register()方法会返回一个SelectionKey对象。这个对象代表了注册到该Selector的通道。可以通过Selector的selectedKeys()方法访问这些对象
wakeUp()
某个线程调用select()方法之后阻塞了,即使没有通道已经就绪,也可以让其返回。只要让其他线程在第一个线程调用select()方法的对象上调用Selector.wakeUp()方法即可,阻塞在select()方法上的线程会立马返回
如果有其他线程调用了wakeUp()方法,且线程调用select()方法没有阻塞,下次调用select()方法的线程会立即醒来
close()
用完Selector后调用close()方法关闭,且是注册到Selector上的所有SelectionKey实例无效,通道本身并不会关闭