Java NIO Selector选择器源码分析

文章目录

  • 前言
  • Selector类结构
  • Selector抽象类
  • AbstractSelector
  • SelectorImpl
  • WindowsSelectorImpl
  • 三种SelectionKey集合


前言

Java NIO(New I/O)的Selector选择器是一个用于多路复用(Multiplexing)的I/O操作的关键组件。它允许一个单独的线程监视多个通道(Channel)的可读性和可写性,从而有效地管理大量并发连接。

Selector类结构

在这里插入图片描述

Selector抽象类

public abstract class Selector implements Closeable {protected Selector() { }// 创建Selector对象public static Selector open() throws IOException {return SelectorProvider.provider().openSelector();}// 检测Selector是否打开public abstract boolean isOpen();// 返回创建该Selector的Providerpublic abstract SelectorProvider provider();// 返回Key集合,key集合不能被直接修改,只有在被cancel和channel被撤销的时候key才被移除。并且不是线程安全的集合。public abstract Set<SelectionKey> keys();// 返回selected-key集合,key可以直接移除,但是不可以直接增加。并且不是线程安全的集合。public abstract Set<SelectionKey> selectedKeys();// 选择channel有IO事件的key。// 该方法是非阻塞的selection操作,如果自上次selection操作之后无channel具有IO事件,该方法会立刻返回零。// 执行该方法会立刻清除之前执行的wakeup影响。public abstract int selectNow() throws IOException;// 阻塞操作,只有在以下的状态变化时://(1)至少有一个IO的channel(2)调用selector.wakeup方法(3)当前线程被interrupt(4)timeout时间到(毫秒)public abstract int select(long timeout)throws IOException;// 阻塞操作,返回条件与select(long timeout)类似public abstract int select() throws IOException;// 唤醒当前select阻塞的操作:如果另一个线程当前阻塞在select或select(long)方法。// 如果当前没有select阻塞,则下次执行select或select(long)则直接返回,除非selectNow同时执行;//之后select和select(long)方法会正常阻塞;// 如果在select操作之间多次调用wakeup与调用一次效果是一样的public abstract Selector wakeup();// 关闭Selector。// 调用close方法,如果当前阻塞在selection操作,就像调用wakeup方法一样会立刻中断操作// 与该selector关联的未cancelled的key将失效,它们的channel将撤销,与Selector相关的其他资源将释放。// 如果Selector已经关闭,执行这个方法将没有影响。// selector关闭之后,如果执行与selector相关的操作会报ClosedSelectorExceptionpublic abstract void close() throws IOException;}
// java.nio.channels.spi.SelectorProviderpublic static SelectorProvider provider() {synchronized (lock) {if (provider != null)return provider;return AccessController.doPrivileged(new PrivilegedAction<SelectorProvider>() {public SelectorProvider run() {if (loadProviderFromProperty())return provider;if (loadProviderAsService())return provider;// 这里就是打开Selector的真正方法provider = sun.nio.ch.DefaultSelectorProvider.create();return provider;}});}
}

AbstractSelector

AbstractSelector主要实现了Selector的打开关闭的状态维护,支持异步关闭和中断的begin和end方法,cancelledKeys等。

public abstract class AbstractSelectorextends Selector
{private AtomicBoolean selectorOpen = new AtomicBoolean(true); // 是否打开// The provider that created this selectorprivate final SelectorProvider provider;protected AbstractSelector(SelectorProvider provider) {this.provider = provider;}// 三大key集合之一cancelledKeysprivate final Set<SelectionKey> cancelledKeys = new HashSet<SelectionKey>();void cancel(SelectionKey k) {                       // package-privatesynchronized (cancelledKeys) {cancelledKeys.add(k);}}public final void close() throws IOException {boolean open = selectorOpen.getAndSet(false);if (!open)return;implCloseSelector();// 只有在Selector未关闭的情况下调用,并且只能被调用一次。}// 关闭Selector// 这个方法被close方法调用去执行Selector的关闭操作,只有在Selector未关闭的情况下调用,并且只能被调用一次。具体参考上面close实现protected abstract void implCloseSelector() throws IOException;public final boolean isOpen() {return selectorOpen.get();}public final SelectorProvider provider() {return provider;}protected final Set<SelectionKey> cancelledKeys() {return cancelledKeys;}// 为Selector注册Channel,这个方法被AbstractSelectableChannel.register方法调用protected abstract SelectionKey register(AbstractSelectableChannel ch,int ops, Object att);protected final void deregister(AbstractSelectionKey key) {((AbstractSelectableChannel)key.channel()).removeKey(key);}// -- Interruption machinery --private Interruptible interruptor = null;// 支持异步关闭和中断的begin和end方法protected final void begin() {if (interruptor == null) {interruptor = new Interruptible() {public void interrupt(Thread ignore) {AbstractSelector.this.wakeup();}};}AbstractInterruptibleChannel.blockedOn(interruptor);Thread me = Thread.currentThread();if (me.isInterrupted())interruptor.interrupt(me);}protected final void end() {AbstractInterruptibleChannel.blockedOn(null);}}  

SelectorImpl

SelectorImplSelector 的一个实现类,它通常不会被应用程序直接使用,而是通过 Selector.open() 方法获取一个 Selector 实例,这个实例内部可能是一个 SelectorImpl

以下是 SelectorImpl 的一些关键功能:

  1. 注册通道:通过 register() 方法,可以将一个通道(Channel)注册到选择器(Selector)上,并指定感兴趣的操作集(如 SelectionKey.OP_READSelectionKey.OP_WRITE 等)。
  2. 选择操作select() 方法允许选择器等待,直到至少有一个已注册的通道准备好进行感兴趣的操作。当 select() 方法返回时,可以通过 selectedKeys() 方法获取一个包含已就绪通道的 SelectionKey 集合。
  3. 处理已就绪的通道:一旦通过 select() 方法得知哪些通道已就绪,就可以遍历 selectedKeys() 返回的集合,并对每个已就绪的通道进行相应的处理。
  4. 取消注册和关闭:可以通过 SelectionKeycancel() 方法取消通道的注册,也可以通过 close() 方法关闭选择器。

需要注意的是,SelectorImpl 的具体实现因 Java 的不同版本和不同的操作系统而有所不同。因此,在编写依赖于 SelectorImpl 的代码时,应该尽量使用 SelectorSelectionKey 等抽象接口,以确保代码的兼容性和可移植性。

public abstract class SelectorImpl extends AbstractSelector {protected Set<SelectionKey> selectedKeys = new HashSet();protected HashSet<SelectionKey> keys = new HashSet();private Set<SelectionKey> publicKeys;private Set<SelectionKey> publicSelectedKeys;protected SelectorImpl(SelectorProvider var1) {super(var1);if (Util.atBugLevel("1.4")) {this.publicKeys = this.keys;this.publicSelectedKeys = this.selectedKeys;} else {this.publicKeys = Collections.unmodifiableSet(this.keys);this.publicSelectedKeys = Util.ungrowableSet(this.selectedKeys);}}public Set<SelectionKey> keys() {if (!this.isOpen() && !Util.atBugLevel("1.4")) {throw new ClosedSelectorException();} else {return this.publicKeys;}}public Set<SelectionKey> selectedKeys() {if (!this.isOpen() && !Util.atBugLevel("1.4")) {throw new ClosedSelectorException();} else {return this.publicSelectedKeys;}}protected abstract int doSelect(long var1) throws IOException;private int lockAndDoSelect(long var1) throws IOException {synchronized(this) {if (!this.isOpen()) {throw new ClosedSelectorException();} else {int var10000;synchronized(this.publicKeys) {synchronized(this.publicSelectedKeys) {var10000 = this.doSelect(var1);}}return var10000;}}}public int select(long var1) throws IOException {if (var1 < 0L) {throw new IllegalArgumentException("Negative timeout");} else {return this.lockAndDoSelect(var1 == 0L ? -1L : var1);}}public int select() throws IOException {return this.select(0L);}public int selectNow() throws IOException {return this.lockAndDoSelect(0L);}public void implCloseSelector() throws IOException {this.wakeup();synchronized(this) {synchronized(this.publicKeys) {synchronized(this.publicSelectedKeys) {this.implClose();}}}}protected abstract void implClose() throws IOException;public void putEventOps(SelectionKeyImpl var1, int var2) {}protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {if (!(var1 instanceof SelChImpl)) {throw new IllegalSelectorException();} else {SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);var4.attach(var3);synchronized(this.publicKeys) {this.implRegister(var4);}var4.interestOps(var2);return var4;}}protected abstract void implRegister(SelectionKeyImpl var1);void processDeregisterQueue() throws IOException {Set var1 = this.cancelledKeys();synchronized(var1) {if (!var1.isEmpty()) {Iterator var3 = var1.iterator();while(var3.hasNext()) {SelectionKeyImpl var4 = (SelectionKeyImpl)var3.next();try {this.implDereg(var4);} catch (SocketException var11) {throw new IOException("Error deregistering key", var11);} finally {var3.remove();}}}}}protected abstract void implDereg(SelectionKeyImpl var1) throws IOException;public abstract Selector wakeup();
}

WindowsSelectorImpl

//poll数组和channel数组的初始容量
private final int INIT_CAP = 8;
//select操作时,每个线程处理的最大FD数量。为INIT_CAP乘以2的幂
private final static int MAX_SELECTABLE_FDS = 1024;
//由这个选择器服务的SelectableChannel的列表
private SelectionKeyImpl[] channelArray = new SelectionKeyImpl[INIT_CAP];
//存放所有FD的包装器,主要用于poll操作
private PollArrayWrapper pollWrapper;
//注册到当前选择器上总的通道数量,初始化为1是因为实例化选择器时加入了wakeupSourceFd
private int totalChannels = 1;
//选择操作所需要的辅助线程数量。每增加一组MAX_SELECTABLE_FDS - 1个通道,就需要一个线程。
private int threadsCount = 0;
//辅助线程列表
private final List<SelectThread> threads = new ArrayList();
//创建一个Pipe实例,用于实现唤醒选择器的功能
private final Pipe wakeupPipe ;
//管道的read端FD,用于实现唤醒选择器的功能
private final int wakeupSourceFd;
//管道的write端FD,用于实现唤醒选择器的功能
private final int wakeupSinkFd;
//关闭锁,通常在注册、注销,关闭,修改选择键的interestOps时都存在竞态条件,主要保护channelArray、pollWrapper等
private Object closeLock = new Object();
//FD为键,SelectionKeyImpl为value的内部map,方便通过FD查找SelectionKeyImpl
private final FdMap fdMap = new FdMap();
//内部类SubSelector中封装了发起poll调用和处理poll调用结果的细节。由主线程调用
private final SubSelector subSelector = new SubSelector();
//选择器每次选择的超时参数
private long timeout;
//中断锁,用于保护唤醒选择器使用的相关竞态资源,如interruptTriggered
private final Object interruptLock = new Object();
//是否触发中断,唤醒选择器的重要标志,由interruptLock保护
private volatile boolean interruptTriggered = false;
//启动锁,当使用多线程处理选择器上Channel的就绪事件时,用于协调这些线程向内核发起系统调用
//辅助线程会在该锁上等待
private final WindowsSelectorImpl.StartLock startLock = new WindowsSelectorImpl.StartLock();
//完成锁,当使用多线程处理选择器上Channel的就绪事件时,用于协调这些线程从系统调用中返回
//主线程会在该锁上等待
private final WindowsSelectorImpl.FinishLock finishLock = new WindowsSelectorImpl.FinishLock();
//updateSelectedKeys调用计数器
//SubSelector.fdsMap中的每个条目都有一个的updateCount值。调用processFDSet时,当我们增加numKeysUpdated,
//会同步将updateCount设置为当前值。 这用于避免多次计算同一个选择键更新多次numKeysUpdated。
//同一个选择键可能出现在readfds和writefds中。
private long updateCount = 0L;

三种SelectionKey集合

  • 在Java NIO中,Selector对象维护了三种与SelectionKey相关的集合,这些集合在Selector的生命周期中扮演着重要角色。这三种集合分别是:
  1. 键集(Key Set)

    • 这个集合包含了所有注册到当前Selector对象的通道的SelectionKey对象。换句话说,每当一个通道通过register()方法注册到Selector时,都会生成一个与之对应的SelectionKey,并添加到这个键集中。这个集合可以通过调用Selectorkeys()方法获得。
    • 需要注意的是,键集是包含了所有注册到选择器的通道的集合,无论这些通道的事件是否就绪。
  2. 已选择键集(Selected Key Set)

    • 当调用Selectorselect()方法时,它会阻塞等待,直到至少有一个注册到它的通道上的某个事件变得就绪(例如,可读、可写等)。一旦有事件就绪,这些事件对应的SelectionKey就会被自动加入到已选择键集中。
    • 这个集合包含了上一次Selector选择期间,发生了就绪事件的通道的SelectionKey对象集合。它是键集的子集,可以通过调用SelectorselectedKeys()方法获得。
    • 在处理完一个SelectionKey后,通常需要从已选择键集中移除它,以避免重复处理。
  3. 已取消键集(Cancelled Key Set)

    • 这个集合包含了那些已经被调用cancel()方法取消的SelectionKey对象,但是关联的通道还没有被撤销。这个集合不能直接获得,并且它始终是键集的子集。
    • 当一个SelectionKey被取消后,它并不会立即从键集中移除,而是会被加入到已取消键集中。在下次调用select()方法时,这些已取消的键会被从键集中移除。

这三种集合共同协作,使得Selector能够高效地管理多个通道的事件,并通过非阻塞的方式处理这些事件。开发者可以通过操作这些集合来监控通道的状态,处理就绪的事件,以及管理通道的注册和取消操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/795370.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【题目】【网络系统管理】2021年全国职业院校技能大赛模块B--样题(九)

2021年全国职业院校技能大赛 网络系统管理&#xff08;样题9&#xff09;模块B&#xff1a;Windows环境 全国职业院校技能大赛执委会.技术专家组 2021年03月 竞赛简介 请认真阅读以下指引&#xff01; 比赛共4个小时&#xff0c;你必须自行决定如何分配你的时间。 当比赛结…

java-权限修饰符、代码块

一、权限修饰符概念 权限修饰符是用来控制一个成员被访问的范围&#xff0c;可以用来修饰成员变量、方法、构造方法、内部类 二、权限修饰符的分类 举例&#xff1a; 1、private 2、空着不写 3、protected 4、public 三、权限修饰符的使用规则 实际开发中&#xff0c;一般使…

Mahalanobis距离(马氏距离)的本质

马氏距离是加权 ℓ 2 \ell_2 ℓ2​范数的特例。 马氏距离是一种基于样本分布的距离&#xff0c;加权矩阵是样本或总体协方差矩阵的逆&#xff0c;其本质为去相关数据标准化&#xff0c;通过数据变换&#xff0c;消除样本中不同特征维度间的相关性和量纲差异。

电子台账:用控件颜色提高工作效率和数据质量

目录 1 前言 2 用页签颜色表示月度数据锁定状态 3 模板制作中定位数据源表格及其行列 3.1 鼠标移过水平过滤模板 3.2 鼠标移过垂直过滤模板 4 数据抓取过程对账页和源单元格同时染色 5 数据溯源过程&#xff0c;对企业数据源单元格染色 6 用键盘进行数据编辑后 1 前言 …

第1个Django应用及Django的请求处理

Python学习之路系列文章目录 python面向对象之警察与匪徒火拼场景模拟python面向对像之第二次笔记Django环境搭建及测试第1个Django应用及Django的请求处理 第1个Django应用及Django的请求处理 Python学习之路系列文章目录一、PyCharm创建django项目二、创建app什么是app怎么创…

JavaScript权威指南(第7版) 笔记 - 扩展操作符总结

扩展操作符 ... &#xff0c;不是真正意义上的JavaScript操作符。 let str "0123ABC" console.log(typeof ...str);// Uncaught SyntaxError: Unexpected token ... 上面的第2行代码会报错&#xff0c;扩展操作符 ... 只能在数组字面量、对象字面量、函数调用中使…

C语言中的字符与字符串:魔法般的函数探险

前言 在C语言的世界里&#xff0c;字符和字符串是两个不可或缺的元素&#xff0c;它们像是魔法般的存在&#xff0c;让文字与代码交织出无限可能。而在这个世界里&#xff0c;有一批特殊的函数&#xff0c;它们如同探险家&#xff0c;引领我们深入字符与字符串的秘境&#xff0…

Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?

Linux&#xff1a;进程等待究竟是什么&#xff1f;如何解决子进程僵尸所带来的内存泄漏问题&#xff1f; 一、进程等待的概念二、进程等待存在的意义三、如何进行进程等待3.1 wait()是实现进程等待1、wait()原型2. 验证wait()能回收僵尸子进程的空间 3.2 waitpid()实现进程等待…

560.和为K的子数组

560.和为K的子数组 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1], k 2 输出&#xff1a;2示例 2&#xff1a; 输入&#xf…

Win10 下 git error unable to create file Invalid argument 踩坑实录

原始解决方案参看&#xff1a;https://stackoverflow.com/questions/26097568/git-pull-error-unable-to-create-file-invalid-argument 本问题解决于 2024-02-18&#xff0c;使用 git 版本 2.28.0.windows.1 解决方案 看 Git 抛出的出错的具体信息&#xff0c;比如如下都来自…

c51 单片机如何控制小灯闪烁?

目录 硬件电路设计 软件编程 烧录程序 测试 调整和优化 C51单片机是一种经典的8位微控制器&#xff0c;广泛应用于各种嵌入式系统和智能控制项目中。 C51单片机控制小灯闪烁主要涉及到硬件电路设计和软件编程两个方面。下面是一个基本的步骤说明&#xff1a; 硬件电路设计…

铸铁平台合理布局的重要性

铸铁平台合理布局的重要性是为了确保工作环境的安全和效率。以下是一些重要的原因&#xff1a; 安全性&#xff1a;合理布局可以最大限度地减少工作场所的事故和伤害。通过将设备和材料放置在正确的位置&#xff0c;可以降低工作人员被危险物体击中或跌倒的风险。此外&#xff…

【瑞萨RA6M3】1. 基于 vscode 搭建开发环境

基于 vscode 搭建开发环境 1. 准备2. 安装2.1. 安装瑞萨软件包2.2. 安装编译器2.3. 安装 cmake2.4. 安装 openocd2.5. 安装 ninja2.6. 安装 make 3. 生成初始代码4. 修改 cmake 脚本5. 调试准备6. 仿真 1. 准备 需要瑞萨仓库中的两个软件&#xff1a; MDK_Device_Packs.zipse…

Android 代码自定义drawble文件实现View圆角背景

简介 相信大多数Android开发都会遇到一个场景&#xff0c;给TextView或Button添加背景颜色&#xff0c;修改圆角&#xff0c;描边等需求。一看到这样的实现效果&#xff0c;自然就是创建drawble文件&#xff0c;设置相关属性shap&#xff0c;color&#xff0c;radius等。然后将…

基于单片机电流变送器系统仿真设计

**单片机设计介绍&#xff0c;基于单片机电流变送器系统仿真设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机电流变送器系统的仿真设计&#xff0c;主要目标是利用仿真技术&#xff0c;模拟单片机与电流变送器之间…

二分答案 蓝桥杯 2022 省A 青蛙过河

有些地方需要解释&#xff1a; 1.从学校到家和从家到学校&#xff0c;跳跃都是一样的&#xff0c;直接看作2*x次过河就可以。 2.对于一个跳跃能力 y&#xff0c;青蛙能跳过河 2x 次&#xff0c;当且仅当对于每个长度为 y 的区间&#xff0c;这个区间内 h 的和都大于等于…

hololens 2 投屏 报错

使用Microsoft HoloLens投屏时&#xff0c;ip地址填对了&#xff0c;但是仍然报错&#xff0c;说hololens 2没有打开&#xff0c; 首先检查 开发人员选项 都打开&#xff0c;设备门户也打开 然后检查系统–体验共享&#xff0c;把共享都打开就可以了

计算机网络—HTTP协议:深入解析与应用实践

​ &#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;ヒステリックナイトガール 1:03━━━━━━️&#x1f49f;──────── 5:06 &#x1f504; ◀️ ⏸ ▶️ ☰…

java算法day45 | 动态规划part07 ● 70. 爬楼梯 (进阶) ● 322. 零钱兑换 ● 279.完全平方数

70. 爬楼梯 &#xff08;进阶&#xff09; 题目描述&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬至多m (1 < m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 注意&#xff1a;给定 n 是一个正整数。 输入描述&#xff1a;输入…

java对象是怎么在jvm中new出来的

java对象是怎么在jvm中new出来的 查看java对象字段属性在内存中的值 java 对象 创建 流程 附上java源码 public class MiDept {private int innerFiled999;public MiDept() {System.out.println("new MiDept--------------");}public String show(int data) {Sy…