文档章节

07. Java NIO Selector选择器

逝去的回忆
 逝去的回忆
发布于 2016/11/21 15:07
字数 1731
阅读 9
收藏 0
点赞 0
评论 0

Selector是Java NIO中的一个组件,用于检查一个或多个NIO Channel的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。

为什么使用Selector(Why Use a Selector?)

用单线程处理多个channels的好处是我需要更少的线程来处理channel。实际上,你甚至可以用一个线程来处理所有的channels。从操作系统的角度来看,切换线程开销是比较昂贵的,并且每个线程都需要占用系统资源,因此暂用线程越少越好。

需要留意的是,现代操作系统和CPU在多任务处理上已经变得越来越好,所以多线程带来的影响也越来越小。如果一个CPU是多核的,如果不执行多任务反而是浪费了机器的性能。不过这些设计讨论是另外的话题了。简而言之,通过Selector我们可以实现单线程操作多个channel。

这有一幅示意图,描述了单线程处理三个channel的情况:

overview-selectors.png

Java NIO: A Thread uses a Selector to handle 3 Channel's

创建Selector(Creating a Selector)

创建一个Selector可以通过Selector.open()方法:

Selector selector = Selector.open();

注册Channel到Selector上(Registering Channels with the Selector)

为了同Selector挂了Channel,我们必须先把Channel注册到Selector上,这个操作使用SelectableChannel。register():

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

Channel必须是非阻塞的。所以FileChannel不适用Selector,因为FileChannel不能切换为非阻塞模式。Socket channel可以正常使用。

注意register的第二个参数,这个参数是一个“关注集合”,代表我们关注的channel状态,有四种基础类型可供监听:

  1. Connect
  2. Accept
  3. Read
  4. Write

一个channel触发了一个事件也可视作该事件处于就绪状态。因此当channel与server连接成功后,那么就是“连接就绪”状态。server channel接收请求连接时处于“可连接就绪”状态。channel有数据可读时处于“读就绪”状态。channel可以进行数据写入时处于“写就绪”状态。

上述的四种就绪状态用SelectionKey中的常量表示如下:

  1. SelectionKey.OP_CONNECT
  2. SelectionKey.OP_ACCEPT
  3. SelectionKey.OP_READ
  4. SelectionKey.OP_WRITE

如果对多个事件感兴趣可利用位的或运算结合多个常量,比如:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;    

SelectionKey's

在上一小节中,我们利用register方法把Channel注册到了Selectors上,这个方法的返回值是SelectionKeys,这个返回的对象包含了一些比较有价值的属性:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)

这5个属性都代表什么含义呢?下面会一一介绍。

Interest Set

这个“关注集合”实际上就是我们希望处理的事件的集合,它的值就是注册时传入的参数,我们可以用按为与运算把每个事件取出来:

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE; 

Ready Set

"就绪集合"中的值是当前channel处于就绪的值,一般来说在调用了select方法后都会需要用到就绪状态,select的介绍在胡须文章中继续展开。

int readySet = selectionKey.readyOps();

从“就绪集合”中取值的操作类似月“关注集合”的操作,当然还有更简单的方法,SelectionKey提供了一系列返回值为boolean的的方法:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

Channel + Selector

从SelectionKey操作Channel和Selector非常简单:

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();    

Attaching Objects

我们可以给一个SelectionKey附加一个Object,这样做一方面可以方便我们识别某个特定的channel,同时也增加了channel相关的附加信息。例如,可以把用于channel的buffer附加到SelectionKey上:

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();

附加对象的操作也可以在register的时候就执行:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

从Selector中选择channel(Selecting Channels via a Selector)

一旦我们向Selector注册了一个或多个channel后,就可以调用select来获取channel。select方法会返回所有处于就绪状态的channel。 select方法具体如下:

  • int select()
  • int select(long timeout)
  • int selectNow()

select()方法在返回channel之前处于阻塞状态。 select(long timeout)和select做的事一样,不过他的阻塞有一个超时限制。

selectNow()不会阻塞,根据当前状态立刻返回合适的channel。

select()方法的返回值是一个int整形,代表有多少channel处于就绪了。也就是自上一次select后有多少channel进入就绪。举例来说,假设第一次调用select时正好有一个channel就绪,那么返回值是1,并且对这个channel做任何处理,接着再次调用select,此时恰好又有一个新的channel就绪,那么返回值还是1,现在我们一共有两个channel处于就绪,但是在每次调用select时只有一个channel是就绪的。

selectedKeys()

在调用select并返回了有channel就绪之后,可以通过选中的key集合来获取channel,这个操作通过调用selectedKeys()方法:

Set<SelectionKey> selectedKeys = selector.selectedKeys();    

还记得在register时的操作吧,我们register后的返回值就是SelectionKey实例,也就是我们现在通过selectedKeys()方法所返回的SelectionKey。

遍历这些SelectionKey可以通过如下方法:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
}

上述循环会迭代key集合,针对每个key我们单独判断他是处于何种就绪状态。

注意keyIterater.remove()方法的调用,Selector本身并不会移除SelectionKey对象,这个操作需要我们收到执行。当下次channel处于就绪是,Selector任然会吧这些key再次加入进来。

SelectionKey.channel返回的channel实例需要强转为我们实际使用的具体的channel类型,例如ServerSocketChannel或SocketChannel.

wakeUp()

y由于调用select而被阻塞的线程,可以通过调用Selector.wakeup()来唤醒即便此时已然没有channel处于就绪状态。具体操作是,在另外一个线程调用wakeup,被阻塞与select方法的线程就会立刻返回。

close()

当操作Selector完毕后,需要调用close方法。close的调用会关闭Selector并使相关的SelectionKey都无效。channel本身不管被关闭。

完整的Selector案例(Full Selector Example)

这有一个完整的案例,首先打开一个Selector,然后注册channel,最后锦亭Selector的状态:

Selector selector = Selector.open();

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

while(true) {

  int readyChannels = selector.select();

  if(readyChannels == 0) continue;

  Set<SelectionKey> selectedKeys = selector.selectedKeys();

  Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

  while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
  }
}

© 著作权归作者所有

共有 人打赏支持
逝去的回忆
粉丝 12
博文 132
码字总数 219842
作品 0
深圳
高级程序员
Java NIO之Selector(选择器)

历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) Java NIO 之 Channel(通道) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂redis集群原理及搭建与使用 超详细的Jav...

山川_84b6 ⋅ 05/16 ⋅ 0

高性能网络通信框架Netty-Netty客户端底层与Java NIO对应关系

5.1 Netty客户端底层与Java NIO对应关系 在讲解Netty客户端程序时候我们提到指定NioSocketChannel用于创建客户端NIO套接字通道的实例,下面我们来看NioSocketChannel是如何创建一个Java NIO里...

阿里加多 ⋅ 06/04 ⋅ 0

Java NIO 系列教程 -- delete

(一) Java NIO 概述 Java NIO 由以下几个核心部分组成: Channels Buffers Selectors 虽然Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Selector 构成了核心的A...

数据之美 ⋅ 2013/06/09 ⋅ 4

java nio 一个channel如何处理大量请求?

java nio中说:可以将一个selector注册到多个channel上。 但是,一个channel绑定了一个端口,这样我理解,当有大量连接请求连接到这同一个端口时,岂不是让selector这一个线程串行处理这些个...

pinitianjie ⋅ 05/09 ⋅ 0

2018年Java编程学习面试最全知识点总结

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

Java小辰 ⋅ 05/14 ⋅ 0

Java 5 、6、 7中新特性

JDK5新特性(与1.4相比)【转】 1 循环 for (type variable : array){ body} for (type variable : arrayList){body} 而1.4必须是: for (int i = 0; i < array.length; i++){ type variabl......

thinkyoung ⋅ 2014/10/14 ⋅ 0

00.java虚拟机的基本结构概念

java虚拟机的基本结构 1、类加载子系统:负责从文件系统或者网络中加载Class信息,加载的信息存放在一块称之为方法区的内存空间。 2、方法区:就是存放类信息、常量信息、常量池信息、包括字...

挚爱冷如烟° ⋅ 05/08 ⋅ 0

Jenkins 教程(一)实现自动化打包及邮件通知

个人不喜欢装腔作势一堆专业术语放上去,让大多数人看不懂来提升逼格(所谓的专家),所以我简单的介绍jenkins是干啥的。本文使用jenkins,就是让它把git仓库里的东西取出来,然后在jenkins容器...

FantJ ⋅ 05/26 ⋅ 0

阿里巴巴菜鸟Java一面11个问题,你会几个呢?

近日,w3cschool app开发者头条上分享了阿里菜鸟Java程序员一些面试题。 这吸引了不少程序员小伙伴们的注意。 在分享阿里菜鸟Java程序员面经前,来看下Java面试一些面试经验分享: 0、Java高...

W3Cschool ⋅ 04/03 ⋅ 0

Java NIO 机制分析(一) Java IO的演进

一、引言 Java1.4之前的早期版本,Java对I/O的支持并不完善,开发人员再开发高性能I/O程序的时候,会面临一些巨大的挑战和困难,主要有以下一些问题: (1)没有数据缓冲区,I/O性能存在问题...

宸明 ⋅ 04/20 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Spring Boot整合模板引擎thymeleaf

项目结构 引入依赖pom.xml <!-- 引入 thymeleaf 模板依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId......

yysue ⋅ 5分钟前 ⋅ 0

ConstraintLayout使用解析

AndroidStudio3.0创建Project默认的布局就是ConstraintLayout。 AndroidStudio3.0前的可以自己修改,使用ConstraintLayout。 为了要使用ConstraintLayout,我们需要在app/build.gradle文件中...

_OUTMAN_ ⋅ 17分钟前 ⋅ 0

OSChina 周三乱弹 —— 这样的女人私生活太混乱了

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @ 胖达panda :你经历过体验到人生的大起大落吗?我一朋友在10秒内体验了,哈哈。@小小编辑 请点一首《almost lover》送给他。 《almost love...

小小编辑 ⋅ 50分钟前 ⋅ 7

自己动手写一个单链表

文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的微信公众号:好好学java,获取优质学习资源。 一、概述 单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对...

公众号_好好学java ⋅ 56分钟前 ⋅ 0

Centos7重置Mysql 8.0.1 root 密码

问题产生背景: 安装完 最新版的 mysql8.0.1后忘记了密码,向重置root密码;找了网上好多资料都不尽相同,根据自己的问题总结如下: 第一步:修改配置文件免密码登录mysql vim /etc/my.cnf 1...

豆花饭烧土豆 ⋅ 今天 ⋅ 0

熊掌号收录比例对于网站原创数据排名的影响[图]

从去年下半年开始,我在写博客了,因为我觉得业余写写博客也还是很不错的,但是从2017年下半年开始,百度已经推出了原创保护功能和熊掌号平台,为此,我也提交了不少以前的老数据,而这些历史...

原创小博客 ⋅ 今天 ⋅ 0

LVM讲解、磁盘故障小案例

LVM LVM就是动态卷管理,可以将多个硬盘和硬盘分区做成一个逻辑卷,并把这个逻辑卷作为一个整体来统一管理,动态对分区进行扩缩空间大小,安全快捷方便管理。 1.新建分区,更改类型为8e 即L...

蛋黄Yolks ⋅ 今天 ⋅ 0

Hadoop Yarn调度器的选择和使用

一、引言 Yarn在Hadoop的生态系统中担任了资源管理和任务调度的角色。在讨论其构造器之前先简单了解一下Yarn的架构。 上图是Yarn的基本架构,其中ResourceManager是整个架构的核心组件,它负...

p柯西 ⋅ 今天 ⋅ 0

uWSGI + Django @ Ubuntu

创建 Django App Project 创建后, 可以看到路径下有一个wsgi.py的问题 uWSGI运行 直接命令行运行 利用如下命令, 可直接访问 uwsgi --http :8080 --wsgi-file dj/wsgi.py 配置文件 & 运行 [u...

袁祾 ⋅ 今天 ⋅ 0

JVM堆的理解

在JVM中,我们经常提到的就是堆了,堆确实很重要,其实,除了堆之外,还有几个重要的模块,看下图: 大 多数情况下,我们并不需要关心JVM的底层,但是如果了解它的话,对于我们系统调优是非常...

不羁之后 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部