选择器

最后,我们探索一下选择器。由于选择器内容比较多,所以本篇先偏理论地讲一下,后一篇讲代码,文章也没有什么概括、总结的,写到哪儿算哪儿了,只求能将选择器写明白,并且将一些相对重要的内容加粗标红。

选择器提供选择执行已经就绪的任务的能力,这使得多元I/O成为了可能,就绪执行和多元选择使得单线程能够有效地同时管理多个I/O通道。

某种程度上来说,理解选择器比理解缓冲区和通道类更困难一些和复杂一些,因为涉及了三个主要的类,它们都会同时参与到这整个过程中,这里先将选择器的执行分解为几条细节:

1、创建一个或者多个可选择的通道(SelectableChannel)

2、将这些创建的通道注册到选择器对象中

3、选择键会记住开发者关心的通道,它们也会追踪对应的通道是否已经就绪

4、开发者调用一个选择器对象的select()方法,当方法从阻塞状态返回时,选择键会被更新

5、获取选择键的集合,找到当时已经就绪的通道,通过遍历这些键,开发者可以选择对已就绪的通道要做的操作

对于选择器的操作,大致就是这么几步,OK,接下去再进一步,看一下和选择器相关的三个类。

选择器、可选择通道和选择键类

选择器(Selector)

选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。

可选择通道(SelectableChannel)

这个抽象类提供了实现通道的可选择性所需要的公共方法,它是所有支持就绪检查的通道类的父类,FileChannel对象不是可选择的,因为它们没有继承SelectableChannel,所有Socket通道都是可选择的,包括从管道(Pipe)对象中获得的通道。SelectableChannel可以被注册到Selector对象上,同时可以设定对哪个选择器而言哪种操作是感兴趣的。一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。

选择键(SelectionKey)

选择键封装了特定的通道与特定的选择器的注册关系。调用SelectableChannel.register()方法会返回选择键并提供一个表示这种注册关系的标记。选择键包含了两个比特集(以整数形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。

用一张UML图来描述一下选择器、可选择通道和选择键:

建立选择器

前面讲了,选择器的作用是管理了被注册的通道集合和它们的就绪状态,假设我们有三个Socket通道的选择器,可能会有类似的代码:

...
Selector selector = Selector.open();
channel1.register(selector, SelectionKey.OP_READ);
channel2.register(selector, SelectionKey.OP_WRITE);
channel3.register(selector, SelectionKey.OP_READ | OP_WRITE);
channel4.register(selector, SelectionKey.OP_READ | OP_ACCEPT);
ready = selector.select(10000);
...

这种操作用图表示就是:

代码创建了一个新的选择器,然后将这四个(已经存在的)Socket通道注册到选择器上,而且感兴趣的操作各不相同。select()方法在将线程置于睡眠状态直到这些感兴趣的事件中的一个发生或者10秒钟过去,这就是所谓的事件驱动

再稍微看一下Selector的API细节:

public abstract class Selector
{
...
public static Selector open() throws IOException;
public abstract boolean isOpen();
public abstract void close() throws IOException;
public abstract SelectionProvider provider();
...
}

Selector是通过调用静态工厂方法open()来实例化的,这个从前面的代码里面也看到了,选择器不是像通道或流那样的基本I/O对象----数据从来没有通过他们进行传递

通道是调用register方法注册到选择器上的,从代码里面可以看到register()方法接受一个Selector对象作为参数,以及一个名为ops的整数型参数,第二个参数表示关心的通道操作。在JDK1.4中,有四种被定义的可选择操作:读(read)、写(write)、连接(connect)和接受(accept)。

注意并非所有的操作都在所有的可选择通道上被支持,例如SocketChannel就不支持accept。

使用选择键

接下来看看选择键,选择键的API大致如下:

public abstract class SelectionKey
{
public static final int OP_READ;
public static final int OP_WRITE;
public static final int OP_CONNECT;
public static final int OP_ACCEPT;
public abstract SelectableChannel channel();
public abstract Selector selector();
public abstract void cancel();
public abstract boolean isValid();
public abstract int interestOps();
public abstract void iterestOps(int ops);
public abstract int readyOps();
public final boolean isReadable();
public final boolean isWritable();
public final boolean isConnectable();
public final boolean isAcceptable();
public final Object attach(Object ob);
public final Object attachment();
}

关于这些API,总结几点:

1、就像前面提到的,一个键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系,channel()方法和selector()方法反映了这种关系

2、开发者可以使用cancel()方法终结这种关系,可以使用isValid()方法来检查这种有效的关系是否仍然存在,可以使用readyOps()方法来获取相关的通道已经就绪的操作

3、第2点有提到readyOps()方法,不过我们往往不需要使用这个方法,SelectionKey类定义了四个便于使用的布尔方法来为开发者测试通道的就绪状态,例如:

if (key.isWritable()){...}

这种写法就等价于:

if ((key.readyOps() & SelectionKeys.OPWRITE) != 0){...}

isWritable()、isReadable()、isConnectable()、isAcceptable()四个方法在任意一个SelectionKey对象上都能安全地调用。

4、当通道关闭时,所有相关的键会自动取消(一个通道可以被注册到多个选择器上);当选择器关闭时,所有被注册到该选择器的通道都会被注销并且相关的键立即被取消。

Selector维护的三种键

选择器维护者注册过的通道的集合,并且这些注册关系中的任意一个都是封装在SelectionKey对象中的。每一个Selector对象维护三种键的集合:

public abstract class Selector
{
...
public abstract Set keys();
public abstract Set selectedKeys();
public abstract int select() throws IOException;
public abstract int select(long timeout) throws IOException;
public abstract int selectNow() throws IOException;
public abstract void wakeup();
...
}

由这个API看下去,这三种键是:

已注册的键的集合(Registered key set)

与选择器关联的已经注册的键的集合,并不是所有注册过的键都有效,这个集合通过keys()方法返回,并且可能是空的。这些键的集合是不可以直接修改的,试图这么做将引发java.lang.UnsupportedOperationException。

已选择的键的集合(Selected key set)

已注册的键的集合的子集,这个集合的每个成员都是相关的通道被选择器判断为已经准备好的并且包含于键的interest集合中的操作。这个集合通过selectedKeys()方法返回(有可能是空的)。

键可以直接从这个集合中移除,但不能添加。试图向已选择的键的集合中添加元素将抛出java.lang.UnsupportedOperationException。

已取消的键的集合(Cancelled key set)

已注册的键的集合的子集,这个集合包含了cancel()方法被调用过的键(这个键已经被无效化),但它们还没有被注销。这个集合是选择器对象的私有成员,因而无法直接访问。

选择过程

接着就是Selector的核心选择过程了。基本上来说,选择器是对select()、poll()、epoll()等本地调用或者类似的操作系统特定的系统调用的一个包装。但是Selector所做的不仅仅是简单地向本地代码传送参数,每个操作都有特定的过程,对这个过程的理解是合理地管理键和它们所表示的状态信息的基础。

选择操作是当三种形式的select()中的任意一种被调用时,由选择器执行的。不管是哪一种形式的调用,下面步骤将被执行:

1、已取消的键的集合将会被检查。如果它是非空的,每个已取消的键的集合中的键将从另外两个集合中移除,并且相关的通道将被注销。此步骤结束,已取消的键的集合将是空的。

2、已注册的键的集合中的键的interest集合将被检查,此步骤结束,对interest集合的改动不会影响剩余的检查过程。一旦就绪条件被定下来,底层操作系统将会进行查询,以确定每个通道所关心的操作的真实就绪状态,依赖于特定的select()方法调用,如果没有通道已经准备好,线程可能会在这时阻塞,通常会有一个超时值。

3、步骤2可能会花费很长时间,特别是线程处于阻塞状态时。与该选择器相关的键可能会同时被取消,当步骤2结束时,步骤1将重新执行,以完成任意一个在选择进行的过程中,键已经被取消的通道的注册。

4、select操作的返回值不是已准备好的通道的总数,而是从上一个select()调用之后进入就绪状态的通道的数量。之前的调用中就绪的,并且在本次调用中仍然就绪的通道不会被计入,而那些在前一次调用中已经就绪但已经不再处于就绪状态的通道也不会被计入。

最后,上面的Selector中还有两个方法没有提到,这里说明一下它们的意思:

1、selectNow()

调用selectNow()方法执行就绪检查过程,但不阻塞,如果当前没有通道就绪,立刻返回0.

2、wakeup()

调用wakeup()方法将使得选择器上的第一个还没有返回的选择操作立即返回,如果当前没有正在进行中的选择,那么下一次对select()方法的一种形式的调用将立即返回,后续的选择操作将正常进行。

Java NIO5:选择器1---理论篇的更多相关文章

  1. Java NIO6:选择器1——理论篇

    一.选择器 选择器提供选择执行已经就绪的任务的能力,这使得多元I/O成为了可能,就绪执行和多元选择使得单线程能够有效地同时管理多个I/O通道. 某种程度上来说,理解选择器比理解缓冲区和通道类更困难一些 ...

  2. Lucene核心--构建Lucene搜索(上篇,理论篇)

    2.1构建Lucene搜索 2.1.1 Lucene内容模型 一个文档(document)就是Lucene建立索引和搜索的原子单元,它由一个或者多个字段(field)组成,字段才是Lucene的真实内 ...

  3. Java工程师学习指南 完结篇

    Java工程师学习指南 完结篇 先声明一点,文章里面不会详细到每一步怎么操作,只会提供大致的思路和方向,给大家以启发,如果真的要一步一步指导操作的话,那至少需要一本书的厚度啦. 因为笔者还只是一名在校 ...

  4. Android下基于SDL的位图渲染(二)理论篇

    理论篇 上一篇中介绍了如何将SDL2源码应用到Android渲染中,实际上SDL本身提供的android-project实现了基于android的c运行时环境,通过上面实践篇的介绍,就是完成这个环境搭 ...

  5. 如何编写高质量的 JS 函数(3) --函数式编程[理论篇]

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/EWSqZuujHIRyx8Eb2SSidQ作者:杨昆 [编写高质量函数系列]中, <如何 ...

  6. 大数据理论篇 - 通俗易懂,揭秘谷歌《The Dataflow Model》的核心思想(一)

    目录 前言 目标 核心的设计原则 通用的数据处理流程 切合实际的解决方案 总结 延伸阅读 最后 作者:justmine 头条号:大数据达摩院 创作不易,未经授权,禁止转载,否则保留追究法律责任的权利. ...

  7. 14 微服务电商【黑马乐优商城】:day01-springboot(理论篇)

    本项目的笔记和资料的Download,请点击这一句话自行获取. day01-springboot(理论篇) :day01-springboot(实践篇) :day01-springboot(Thyme ...

  8. 14 微服务电商【黑马乐优商城】:day02-springcloud(理论篇二:知道什么是SpringCloud)

    本项目的笔记和资料的Download,请点击这一句话自行获取. day01-springboot(理论篇) :day01-springboot(实践篇) day02-springcloud(理论篇一: ...

  9. 14 微服务电商【黑马乐优商城】:day02-springcloud(理论篇一:HttpClient的简单使用)

    本项目的笔记和资料的Download,请点击这一句话自行获取. day01-springboot(理论篇) :day01-springboot(实践篇) day02-springcloud(理论篇一: ...

  10. PHP丨PHP基础知识之条件语IF判断「理论篇」

    if语句是指编程语言(包括c语言.C#.VB.java.php.汇编语言等)中用来判定所给定的条件是否满足,根据判定的结果(真或假)决定执行给出的两种操作之一. if语句概述 if语句是指编程语言(包 ...

随机推荐

  1. jquery json数组(排序)

    ar nums = ['12','2','5','36','4']; $('#show7').html(nums.join('<br/>')); //定义了sort的比较函数 nums = ...

  2. HP-SOCKET TCP/UDP通信框架库解析

    项目概述: HP-SOCKET是一套通用TCP/UDP通信框架,包括服务器.客户端.Agent组件:其目标是提供高性能.通用性.简易性.可扩展.可定制: 鉴于此,其仅实现基本的通用框架通信.数据收发功 ...

  3. 使用dd命令备份Linux分区

    为了备份分区,开始使用的是Remastersys,但最终生成的iso文件仅有几十K,应该是软件bug,且此软件不再更新,后尝试使用Linux Respin,但github一直连接不上. 其实可以尝试使 ...

  4. 序言<EntityFramework6.0>

    Entity Framework是微软战略性的数据访问技术,不同与早期访问技术,Entity Framework并不耦合在Visual Studio中,它提供了一个全面的, 基于模型的生态系统,使您能 ...

  5. 当年只会C# 所以写C++就成这样了! log4cplus -> log4net

    属实C++不会. 目前帮朋友弄个小项目需要小折腾一下. c# 一直采用 log4net ,c++的呢,找找有个log4cplus  知识有限,做个通用类吧.别把精力放在这里. 动手创建个静态类. 为了 ...

  6. 开发日志_Jan.6.2017

    Github Jan.2 接到了汤松岩的GUI框架,开始复习和学习C++的使用方法(之前的开发经历主要使用的Java,对C++和QT环境都需要一个再了解). Jan.3 正式开始工作.开始在Ubunt ...

  7. 如何在Windows上从源码编译Chromium (CEF3) 加入mp3支持

    一.什么是CEF CEF即Chromium Embeded Framework,由谷歌的开源浏览器项目Chromium扩展而来,可方便地嵌入其它程序中以得到浏览器功能. CEF包括CEF1和CEF3两 ...

  8. 一个简便的方法,获取某个页面元素的Xpath值

    今天了解到一个比较方便获取页面元素Xpath的方法,以下是获取步骤. 1:使用chrome浏览器打开百度:http://www.baidu.com 2:点击邮件,检查. 3:定位到某个页面的元素:点击 ...

  9. android studio安卓项目出现Error: Default Activity Not Found错误无法编译的解决方案

    项目明明是没有问题的,有时候突然就出现Error: Default Activity Not Found错误,以前出现过我重新安装了android studio 都没有用,后来在网上(http://s ...

  10. Linux下Nano命令使用指南

    1.什么时候用nano? 一般网络很卡,ssh时一用vim/vi 就死窗口,或者死机的情况 2.如何使用?   打开或新建文件 #nano 文件名    禁用自动换行 #nano -w /etc/fs ...