Selector  :

public abstract class Selector

extends Object


SelectableChannel 对象的多路复用器。

可通过调用此类的 open 方法创建选择器,该方法将使用系统的默认选择器提供者创建新的选择器。也可通过调用自定义选择器提供者的
openSelector 方法来创建选择器。通过选择器的
close 方法关闭选择器之前,它一直保持打开状态。

通过 SelectionKey 对象来表示可选择通道到选择器的注册。选择器维护了三种选择键集:

  • 键集 包含的键表示当前通道到此选择器的注册。此集合由 keys 方法返回。

  • 已选择键集 是这样一种键的集合,即在前一次选择操作期间,检测每个键的通道是否已经至少为该键的相关操作集所标识的一个操作准备就绪。此集合由 selectedKeys 方法返回。已选择键集始终是键集的一个子集。

  • 已取消键集 是已被取消但其通道尚未注销的键的集合。不可直接访问此集合。已取消键集始终是键集的一个子集。

在新创建的选择器中,这三个集合都是空集合。

通过某个通道的 register 方法注册该通道时,所带来的副作用是向选择器的键集中添加了一个键。在选择操作期间从键集中移除已取消的键。键集本身是不可直接修改的。

不管是通过关闭某个键的通道还是调用该键的 cancel 方法来取消键,该键都被添加到其选择器的已取消键集中。取消某个键会导致在下一次选择操作期间注销该键的通道,而在注销时将从所有选择器的键集中移除该键。

通过选择操作将键添加到已选择键集中。可通过调用已选择键集的 remove 方法,或者通过调用从该键集获得的 iteratorremove 方法直接移除某个键。通过任何其他方式从不会将键从已选择键集中移除;特别是,它们不会因为影响选择操作而被移除。不能将键直接添加到已选择键集中。

选择

在每次选择操作期间,都可以将键添加到选择器的已选择键集以及从中将其移除,并且可以从其键集和已取消键集中将其移除。选择是由 select()select(long)
selectNow() 方法执行的,执行涉及三个步骤:

  1. 将已取消键集中的每个键从所有键集中移除(如果该键是键集的成员),并注销其通道。此步骤使已取消键集成为空集。

  2. 在开始进行选择操作时,应查询基础操作系统来更新每个剩余通道的准备就绪信息,以执行由其键的相关集合所标识的任意操作。对于已为至少一个这样的操作准备就绪的通道,执行以下两种操作之一:

    1. 如果该通道的键尚未在已选择键集中,则将其添加到该集合中,并修改其准备就绪操作集,以准确地标识那些通道现在已报告为之准备就绪的操作。丢弃准备就绪操作集中以前记录的所有准备就绪信息。

    2. 如果该通道的键已经在已选择键集中,则修改其准备就绪操作集,以准确地标识所有通道已报告为之准备就绪的新操作。保留准备就绪操作集以前记录的所有准备就绪信息;换句话说,基础系统所返回的准备就绪操作集是和该键的当前准备就绪操作集按位分开 (bitwise-disjoined) 的。

    如果在此步骤开始时键集中的所有键都有空的相关集合,则不会更新已选择键集和任意键的准备就绪操作集。

  3. 如果在步骤 (2) 的执行过程中要将任意键添加到已取消键集中,则处理过程如步骤 (1)。

是否阻塞选择操作以等待一个或多个通道准备就绪,如果这样做的话,要等待多久,这是三种选择方法之间的唯一本质差别。

并发性

选择器自身可由多个并发线程安全使用,但是其键集并非如此。

选择操作在选择器本身上、在键集上和在已选择键集上是同步的,顺序也与此顺序相同。在执行上面的步骤 (1) 和 (3) 时,它们在已取消键集上也是同步的。

在执行选择操作的过程中,更改选择器键的相关集合对该操作没有影响;进行下一次选择操作才会看到此更改。

可在任意时间取消键和关闭通道。因此,在一个或多个选择器的键集中出现某个键并不意味着该键是有效的,也不意味着其通道处于打开状态。如果存在另一个线程取消某个键或关闭某个通道的可能性,那么应用程序代码进行同步时应该小心,并且必要时应该检查这些条件。

阻塞在 select()
select(long) 方法之一中的某个线程可能被其他线程以下列三种方式之一中断:

  • 通过调用选择器的 wakeup 方法,

  • 通过调用选择器的 close 方法,或者

  • 在通过调用已阻塞线程的 interrupt 方法的情况下,将设置其中断状态并且将调用该选择器的
    wakeup 方法。

close 方法在选择器上是同步的,并且所有三个键集都与选择操作中的顺序相同。

一般情况下,选择器的键和已选择键集由多个并发线程使用是不安全的。如果这样的线程可以直接修改这些键集之一,那么应该通过对该键集本身进行同步来控制访问。这些键集的
iterator 方法所返回的迭代器是快速失败 的:如果在创建迭代器后以任何方式(调用迭代器自身的
remove 方法除外)修改键集,则会抛出
ConcurrentModificationException

wakeup

public abstract Selector wakeup()
使尚未返回的第一个选择操作立即返回。

如果另一个线程目前正阻塞在 select()select(long) 方法的调用中,则该调用将立即返回。如果当前未进行选择操作,那么在没有同时调用selectNow()
方法的情况下,对上述方法的下一次调用将立即返回。在任一情况下,该调用返回的值可能是非零的。如果未同时再次调用此方法,则照常阻塞select()select(long) 方法的后续调用。

在两个连续的选择操作之间多次调用此方法与只调用一次的效果相同。

返回:
此选择器

selectNow

public abstract int selectNow()
throws IOException
选择一组键,其相应的通道已为 I/O 操作准备就绪。

此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。

调用此方法会清除所有以前调用 wakeup 方法所得的结果。

返回:
由选择操作更新其准备就绪操作集的键的数目,该数目可能为零
抛出:
IOException - 如果发生 I/O 错误
ClosedSelectorException - 如果此选择器已关闭

select

public abstract int select()
throws IOException
选择一组键,其相应的通道已为 I/O 操作准备就绪。

此方法执行处于阻塞模式的选择操作。仅在至少选择一个通道、调用此选择器的 wakeup 方法,或者当前的线程已中断(以先到者为准)后此方法才返回。

返回:
已更新其准备就绪操作集的键的数目,该数目可能为零
抛出:
IOException - 如果发生 I/O 错误
ClosedSelectorException - 如果此选择器已关闭

一、 SelectionKey用完一定移除

Iterator<SelectionKey> it=selector.selectedKeys().iterator();

...for/while it.hasNext()...

it.remove();//键使用完成后必须这样做,或者Set.clear()也行; 否则cpu占用100%,特别是当客户端切断连接(channel)时 。

移除完成以后一定要重新做一下这个操作:

selectionKey.interestOps(selectionKey.interestOps() & (~selectionKey.readyOps));

将事件删除,重新注册

二、Selector.open()不是线程安全的,可能抛出NullPointerException

bug地址:http://bugs.sun.com/view_bug.do?bug_id=6427854


  1. synchronized (Selector.class) {
  2. // Selector.open() isn't thread safe
  3. // Affects 1.6.0_29, fixed in 1.7.0_01
  4. SHARED_SELECTOR = Selector.open();
  5. }

三、如果一个selection thread已经在select方法上等待,那么这个时候如果有另一条线程调用channal.register方法的话,那么它将被blocking.

四、selectionKey.cancel() BUG导致CPU占用100%

bug地址:http://bugs.sun.com/view_bug.do?bug_id=6403933

其原因就是调用key.cancel()时底层在下一次seelect前并没有真正的取消。导致等待select事件返回却又没有返回我们注册的key.这个事件不断地循环触发,CPU一直处理返回 key为0的select()调用。解决方法有两种,一是在key.cancel()后立即selectNow();但是如果是多线程并发操作,有可能这两行语句中间线程被切换,使得key.cancel()后没有立即执行selectNow().这在多Selector情况下是可能的。另一种就是jetty处理方式,如果select()返回0且连续几次出现这样的情况(有事件触发返回,却不是返回我们注册的KEY),就将有效的key重新注册到一个新的selector上。其实glassfish在处理多次次次次write返回为0的情况时也是这种策略。

tomcat7的处理方式:


  1. try{
  2. //对键的操作
  3. }finally{
  4. if (key != null) {
  5.     key.cancel();
  6.     if (selector != null)
  7.      selector.selectNow();// removes the key from this selector
  8.    }
  9. }

Jetty的处理方式:


  1. long before=now;
  2. int selected=selector.select(wait);
  3. now = System.currentTimeMillis();
  4. _idleTimeout.setNow(now);
  5. _timeout.setNow(now);
  6. // Look for JVM bugs
  7. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933
  8. if (__JVMBUG_THRESHHOLD>0 && selected==0 && wait>__JVMBUG_THRESHHOLD && (now-before)<(wait/2) )
  9. {
  10. _jvmBug++;
  11. if (_jvmBug>=(__JVMBUG_THRESHHOLD2))
  12. {
  13. synchronized (this)
  14. {
  15. _lastJVMBug=now;// BLOODY SUN BUG !!! Try refreshing the entire selector.
  16. final Selector new_selector = Selector.open();
  17. for (SelectionKey k: selector.keys())
  18. {
  19. if (!k.isValid() || k.interestOps()==0)
  20. continue;
  21. final SelectableChannel channel = k.channel();
  22. final Object attachment = k.attachment();
  23. if (attachment==null)
  24. addChange(channel);
  25. else
  26. addChange(channel,attachment);
  27. }
  28. _selector.close();
  29. _selector=new_selector;
  30. _jvmBug=0;
  31. return;
  32. }
  33. }
  34. else if (_jvmBug==__JVMBUG_THRESHHOLD || _jvmBug==__JVMBUG_THRESHHOLD1)
  35. {
  36. // Cancel keys with 0 interested ops
  37. for (SelectionKey k: selector.keys())
  38. {
  39. if (k.isValid()&&k.interestOps()==0)
  40. {
  41. k.cancel();
  42. }
  43. }
  44. return;
  45. }
  46. }
  47. else
  48. _jvmBug=0;

五、(so) SocketChannels registered with OP_WRITE only release selector once (win)  CPU 100%

bug地址:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4469394

总结:使用JDK 6 U4以上版本不会出现以上bug(除“Selector.open()不是线程安全的”以外)。

Java Nio注意事项的更多相关文章

  1. JAVA NIO复习笔记

    1. JAVA NIO是什么? 从JDK1.4开始,java提供了一系列改进的输入/输出处理的新功能,这些功能被统称为新IO(New IO,简称NIO),新增了许多用于处理输入/输出的类,这些类都被放 ...

  2. JAVA NIO工作原理及代码示例

    简介:本文主要介绍了JAVA NIO中的Buffer, Channel, Selector的工作原理以及使用它们的若干注意事项,最后是利用它们实现服务器和客户端通信的代码实例. 欢迎探讨,如有错误敬请 ...

  3. Java NIO.2 —— 文件或目录移动操作

    移动文件树是复制和删除的文件树的结合.实际上,有两种方式来完成文件的移动.一种是使用Files.move(), Files.copy(), 和Files.delete() 这三个方法:另一种是只使用F ...

  4. Java NIO.2 —— 文件或目录拷贝操作

    拷贝整个文件树是可以递归每个目录和文件调用 Files.copy()方法.在使用的时候有一下注意事项. 在往目录拷贝文件之前,首先要保证目录已经存在.拷贝源目录(不论是否为空)都会生成目标目录.整个任 ...

  5. Error reading field 'throttle_time_ms': java.nio.BufferUnderflowException

    可能出现的问题: ERROR o.a.k.c.p.i.Sender – Uncaught error in kafka producer I/O thread: org.apache.kafka.co ...

  6. 源码分析netty服务器创建过程vs java nio服务器创建

    1.Java NIO服务端创建 首先,我们通过一个时序图来看下如何创建一个NIO服务端并启动监听,接收多个客户端的连接,进行消息的异步读写. 示例代码(参考文献[2]): import java.io ...

  7. 支撑Java NIO 与 NodeJS的底层技术

    支撑Java NIO 与 NodeJS的底层技术 众所周知在近几个版本的Java中增加了一些对Java NIO.NIO2的支持,与此同时NodeJS技术栈中最为人称道的优势之一就是其高性能IO,那么我 ...

  8. JAVA NIO学习笔记1 - 架构简介

    最近项目中遇到不少NIO相关知识,之前对这块接触得较少,算是我的一个盲区,打算花点时间学习,简单做一点个人学习总结. 简介 NIO(New IO)是JDK1.4以后推出的全新IO API,相比传统IO ...

  9. Java NIO概述

    Java NIO 由以下几个核心部分组成: Channels Buffers Selectors 虽然 Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Se ...

随机推荐

  1. C# 设定弹出窗体位置

    一.C#中弹出窗口位置 加入命名空间 using System.Drawing using System.Windows.Forms 假定窗口名为form1,则 //窗体位置在屏幕中间 form1.S ...

  2. git的使用入门

    写作目的: 快速的上手git版本控制+github神器进行基本的版本同步操作. 怎么做? 对于任意一个代码项目,使用git_bash进入到代码目录 如果没有进行过初始化操作:应当使用git init  ...

  3. 笔记-python-多线程-深入-1

    笔记-python-多线程-深入-1 1.      线程池 1.1.    线程池:控制同时存在的线程数量 threading没有线程池,只能自己控制线程数量. 基本有两种方式: 每间隔一段时间创建 ...

  4. 笔记-redis安装

    笔记-redis安装配置 1.      redis安装配置 1.1.    windows环境安装 win8已有redis 查看版本:redis-server –version 想更新到5.0.0, ...

  5. Android开发——弹性滑动的两种实现方式

    0. 前言   欢迎转载,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52538723 我们在Android开发--View滑动的三 ...

  6. Retrofit get post query filed FiledMap

    直接请求型 1.如果是直接请求某一地址,写法如下: @GET("/record") Call getResult(); 2.如果是组合后直接请求,如/result/{id}写法如下 ...

  7. Java虚拟机之搜索class文件

    Java命令 Java虚拟机的工作是运行Java应用程序.和其他类型的应用程序一样,Java应用程序也需要一个入口点,这个入口点就是我们熟知的main()方法.如果一个类包含main()方法,这个类就 ...

  8. Eclipse 透视图(Perspective)---Eclipse教程第06课

    什么是透视图? 透视图是一个包含一系列视图和内容编辑器的可视容器.默认的透视图叫 java. Eclipse 窗口可以打开多个透视图,但在同一时间只能有一个透视图处于激活状态. 用户可以在两个透视图之 ...

  9. 实现jQuery的$.extend方法

    var o1 = { hello : 1, old : 555 }, o2 = { abc : 55555555, hello : 2, fun : function() { alert(111); ...

  10. Rbac_权限管理

    click!!! https://github.com/ugfly1210/rbac_100 有关于 rbac 的所有代码,包括 README. 用户和角色 : 多对多字段放在哪张表更好点? 用户找角 ...