NIO-Selector类详解

Selector自己为抽象类,AbstractSelector是Selector类的抽象实现类,具体的实现类更加底层(SelectorImpl,位于sun.nio.ch);Selector即为"选择器",支撑了NIO的多路复用.Selector不能直接建立,须要经过Selector.open()得到,该方法将使用系统默认的选择器提供者建立新的选择器(SelectorProvider),能够经过选择器的close方法关闭它.javascript

经过SelectionKey对象来表示可选择通道(SelectableChannel)到选择器的注册.SelectionKey由Selector维护.html

一般一个Channel只会被注册到一个Selector,一个Selector能够“监测”多个Channel;事实上Channel能够注册在任意一个Selector上,ServerSocketChannel和SocketChannel能够共用一个Selector,也能够各自使用不一样的。java

 

一.Selector维护三种选择键:数组

  1.  keys:保存了全部已注册且没有cancel的选择键,Set类型.能够经过selector.keys()获取
  2. selectedKeys:已选择键集,即前一次操做期间,已经准备就绪的通道所对应的选择键.此集合为keys的子集.经过selector.selectedKeys()获取.
  3. canceledKeys:已取消键.已经被取消但还没有取消注册(deregister)的选择键.此集合不可被访问.为keys()的子集.

对于新的Selector实例,上述三个集合均为空.经过channel.register将致使keys()集合被添加.若是某个selectionKey.cancel()被调用,那么此key将会被添加到canceldKey集合中,在下一次selector选择期间,若是canceldKeys不为空,将会致使触发此key的deregister操做(释放资源,并从keys中移除).不管经过channel.close()仍是经过selectionKey.cancel(),都会致使key被加入到cannceldKey中.安全

 

每次选择操做期间(select),均可以将key添加到selectedKeys中或者将从canceledKey中移除.负责"选择"key的操做为select(),selectNow(),select(long timeout);"选择操做"执行的步骤:网络

  1. 首先对cancelledKey进行清除,遍历"已取消键集合",并对每一个key进行deregister操做,而后从"已取消键"集合中删除.从keys集合中删除,从"已选择键"中删除.
  2. 将由更底层的操做来查询操做系统中每一个channel的 准备就绪信息.若是该通道的key还没有在selectedKeys中存在,则将其添加到该集合中.若是该通道的key已经存在selectedKeys中,则修改器opts事件集."选择"通道的就绪信息,在底层是由"可扩展尺寸"的线程池执行,可是在并发较高的IO操做中,这仍然会存在"select"延迟问题.
  3. "选择"操做结束后,再次执行1),已防止在"选择"期间,有些keys被取消.

select是个被同步的过程(将对keys,selectedKey都会同步).多线程调用会被阻塞.多线程

二.并发性:并发

Selector能够在多线程环境中使用,可是其各类键集合并不是是线程安全的.app

Selector自己对各类key集合的操做,都是同步的,固然为了不死锁问题,其同步的顺序也是一致的.好比在执行select操做其余,其余线程register,将会阻塞.能够在任意时刻关闭通道或者取消键.由于select操做并未对cancelledKey同步,所以有可能再selectedKey中出现的key是已经被取消的.这一点须要注意.须要校验:key.isValid() && key.isReadable()...异步

 

三.select()和select(long)是阻塞操做:

  • 经过selector.wakeup()能够将其返回
  • 经过调用selector.close()
  • 调用已阻塞线程的interrupt方法.

注意selector.close()方法也是对keys和selectedKeys进行了同步.通常状况下,keys()和selectedKeys在多线程环境中使用是不安全的.

事实上selector.keys()返回的结果是一个Collections.unmodifiableSet(keys),其中keys参数为selector内部维持的集合.因而可知keys集合是只读的.

selector.selectedKeys()返回的结果是一个Util.ungrowableSet(selectedKeys),其中参数为selector内部维持的集合.因而可知selectedKeys是没法被外部进行add操做的,

可是能够remove以及进行iterator操做.

(参见sun.nio.ch.SelectorImpl,源码来自http://javasourcecode.org/html/open-source/jdk/jdk-6u23/sun/nio/ch/SelectorImpl.java.html)

 

四.方法列表:

  •  public static Selector open() throws IOException:打开一个选择器,此操做将会致使系统默认的SelectorProvider对象的openSelector方法来建立选择器.(此后介绍在各个OS下provider特性)
  • public abstract boolean isOpen():检测此selector是否处于打开状态,仅当selector建立成功却没有关闭,返回true.
  • public abstract Selt<SelectionKey> keys():获取此selector中已经注册(可能已经cancelled但还没有deregister)的全部选择键.此集合不能被外部修改.
  • public abstract Set<SelectionKey> selectedKeys():返回此选择器当前已选择键集合.此集合不能被add,但能够remove操做.
  • public abstract int selectNow() throws IOException:"选择"操做,获取相应的通道已为I/O操做就绪.此方法为非阻塞操做,当即返回已经就绪的的个数.可能为0."非阻塞",意味着,在I/O通道就绪信息的检测上,是无阻塞的,即若是底层线程(底层实现为多线程轮询检测I/O操做,并将已就绪的key放在内部的队列中)所维持的"就绪通道"个数为任意数字都当即返回,固然包括0.由于同一个selector实例中,select(),selectNow(),select(long)底层的实现几乎一致,方法实体中都会对keys和selectedKeys进行同步操做,所以在多线程中同时进行"select"调用,也将会存在互相阻塞的影响.
  • public abstract int select(long timeout) throws IOException:选择一组键,此方法为处于阻塞模式的选择操做.尽在至少一个通道就绪/调用选择器的wakeup/当前线程被interrupt/超时后返回.
  • public abstract int select() throws Exception选择一组键,此方法为阻塞模式的选择操做,直到至少一个通道就绪/wakeup/interrupt时返回.注意Selector支持interrupt(即具备interruptable),它采起了和InterruptableChannel相似的设计思路.即具备begin()/end().

Java代码 

 收藏代码

  1. //伪代码以下:  
  2. doSelect(){  
  3.     try{  
  4.         begin();//注册中断操做  
  5.         select();  
  6.     }finaly{  
  7.         end();//解除中断和检测中断.  
  8.     }  
  9. }  

 

在实际底层I/O选择时(阻塞行为),在方法开始前执行begin():

begin所作的事情,就是向Thread注册interruptable对象,参见AbstractSelector.begin();

Java代码 

  1. if (interruptor == null) {  
  2.    interruptor = new Interruptible() {  
  3.    public void interrupt() {  
  4.         AbstractSelector.this.wakeup();//中断回调,主要操做为让当前selector.wakeup(),即在阻塞中返回.  
  5.    }};  
  6. }  
  7. //注册thread.blocker,向Thread.currentThread()中Interruptable blocker属性赋值.以便在thread.interrupt()时可以执行回调.  
  8. AbstractInterruptibleChannel.blockedOn(interruptor);  
  9. //检测当前thread是否已经被终端.  
  10. if (Thread.currentThread().isInterrupted())  
  11.    interruptor.interrupt();  
  12. }  

 

Java代码 

  1. //Thread.interrupt()方法对异步中断的相应操做  
  2. public void interrupt() {  
  3.     if (this != Thread.currentThread())  
  4.         checkAccess();  
  5.   
  6.     synchronized (blockerLock) {  
  7.         Interruptible b = blocker;  
  8.         if (b != null) {  
  9.         interrupt0();       // Just to set the interrupt flag  
  10.         b.interrupt();  
  11.         return;  
  12.         }  
  13.     }  
  14.     interrupt0();  
  15. }  

 

 在实际IO选择返回后,以及异常将会执行end(),end()方法要比Channel的控制简单,直接取消blocker = null.

 如今,你知道Selector中断和唤醒的机制了吗?

  • public abstract Selector wakeup():使还没有返回的选择操做当即返回.若是另外一个线程目前正阻塞在select()或者select(long)方法的调用中,则该调用将当即返回.返回后,再次调用select等,将会有可能继续阻塞.屡次连续的调用wakeup,效果一致.执行wakeup,将会致使底层Selector实现类的实例的interrupt属性标记为true,而后由JNI调用触发pipe被打断并发回(pipe为selector内部机制,稍后介绍),此后全部在"select"操做上阻塞的线程,依次获取keys和selectedKeys锁,此后将对interrupt属性校验,若是interrupt = true,将会重置pipe(本身给本身创建一个pipe-socket连接),而后再次将interrupt置为false,并从阻塞中返回.因而可知,若是多个线程阻塞,事实上wakeup只能让正在阻塞的一个线程返回.阻塞在select操做上,有2中状况:1)由于keys集合同步阻塞 2) 由于selector IO阻塞...wakeup()方法是让基于pipe IO阻塞的操做返回.可是由于keys同步锁的阻塞它将无能为力.wakeup是一种对底层操做消耗较为严重的操做,须要对此操做的调用频度有所关心.
  • public abstract void close() throws Exception:关闭选择器.若是有其余线程阻塞在此selector的选择操做中,则中断该线程.close()方法内部执行顺序为:
  1. 调用wakeup()方法,使阻塞的线程当即返回.
  2. 关闭selector所关联的底层pipe链接信息.
  3. 对keys和selectedKeys同步,而后遍历此selector所注册是全部的channel(即选择键,底层而言每一个channel对应一个选择键),并依次执行selector.degregister(key),deregister操做将致使每一个channel都删除本身维持的当前selector引用(从内部的key[]数组中删除).若是当前channel已经关闭,则直接销毁当前channel所关联的全部资源(好比关闭打开的文件描述),若是当前channel为open,则保持资源.
  4. 退出全部的底层"select"事件线程池.

 

五.SelectionKey(选择键)

抽象类,表示selectableChannel在Selector中注册的标识.每一个Channel向Selector注册时,都将会建立一个selectionKey.选择键将Channel与Selector创建了关系,并维护了channel事件.能够经过cancel方法取消键,取消的键不会当即从selector中移除,而是添加到cancelledKeys中,在下一次select操做时移除它.因此在调用某个key时,须要使用isValid进行校验.

选择键包含两个操做集,操做集为位运算值,每一位表示一种操做.

  1. interest 集合:当前channel感兴趣的操做,此类操做将会在下一次选择器select操做时被交付,能够经过selectionKey.interestOps(int)进行修改.
  2. ready 集合:表示此选择键上,已经就绪的操做.每次select时,选择器都会对ready集合进行更新;外部程序没法修改此集合.

操做集:

OP_ACCEPT:链接可接受操做,仅ServerSocketChannel支持

OP_CONNECT:链接操做,Client端支持的一种操做

OP_READ/OP_WRITE

 

这些opts都不为0,若是向selector之中register一个为“0”的opts,表示此channel不关注任何类型的事件。(言外之意,register方法只是获取一个selectionKey,具体这个Channel对何种事件感兴趣,能够在稍后操做)

 

方法列表:

  • public abstract SelectableChannel channel():返回此选择键所关联的通道.即便此key已经被取消,仍然会返回.
  • public abstract Selector selector():返回此选择键所关联的选择器,即便此键已经被取消,仍然会返回.
  • public abstract boolean isValid():检测此key是否有效.当key被取消,或者通道被关闭,或者selector被关闭,都将致使此key无效.在AbstractSelector.removeKey(key)中,会致使selectionKey被置为无效.
  • public abstract void cancel():请求将此键取消注册.一旦返回成功,那么该键就是无效的,被添加到selector的cancelledKeys中.cancel操做将key的valid属性置为false,并执行selector.cancel(key)(即将key加入cancelledkey集合)
  • public abstract int interesOps():得到此键的interes集合.
  • public abstract SelectionKey interestOps(int ops):将此键的interst设置为指定值.此操做会对ops和channel.validOps进行校验.若是此ops不会当前channel支持,将抛出异常.
  • public abstract int readyOps():获取此键上ready操做集合.即在当前通道上已经就绪的事件.
  • public final boolean isReadable(): 检测此键是否为"read"事件.等效于:k.,readyOps() & OP_READ != 0;还有isWritable(),isConnectable(),isAcceptable()
  • public final Object attach(Object ob):将给定的对象做为附件添加到此key上.在key有效期间,附件能够在多个ops事件中传递.
  • public final Object attachment():获取附件.一个channel的附件,能够再当前Channel(或者说是SelectionKey)生命周期中共享,可是attachment数据不会做为socket数据在网络中传输.
相关文章
相关标签/搜索