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的Provider
    public 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相关的操作会报ClosedSelectorException
    public abstract void close() throws IOException;
 
}
// java.nio.channels.spi.SelectorProvider
    public 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 AbstractSelector
    extends Selector
{
    private AtomicBoolean selectorOpen = new AtomicBoolean(true); // 是否打开
    // The provider that created this selector
    private final SelectorProvider provider;
    protected AbstractSelector(SelectorProvider provider) {
        this.provider = provider;
    }
    // 三大key集合之一cancelledKeys
    private final Set<SelectionKey> cancelledKeys = new HashSet<SelectionKey>();
 
    void cancel(SelectionKey k) {                       // package-private
        synchronized (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能够高效地管理多个通道的事件,并通过非阻塞的方式处理这些事件。开发者可以通过操作这些集合来监控通道的状态,处理就绪的事件,以及管理通道的注册和取消操作。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JVM基础篇

初识JVM Java虚拟机的组成 字节码文件 i与1 javap ideajclasslib arthas(线上运行的)

无问芯穹 MaaS AI 平台公测免费试用笔记:二

上一篇笔记中&#xff0c;聊过了无问芯穹的 MaaS 服务中的“虚拟机”产品。本篇文章来聊聊最近宣传中提到的大手笔免费百亿 Token 用量的“大模型服务平台” 吧。 分享下这个支持异构芯片推理的国产 “Replicate”、模型市场服务使用的经验和小技巧。 写在前面 本篇文章根据…

openGauss学习笔记-254 openGauss性能调优-使用Plan Hint进行调优-子链接块名的hint

文章目录 openGauss学习笔记-254 openGauss性能调优-使用Plan Hint进行调优-子链接块名的hint254.1 功能描述254.2 语法格式254.3 参数说明254.4 示例 openGauss学习笔记-254 openGauss性能调优-使用Plan Hint进行调优-子链接块名的hint 254.1 功能描述 指明子链接块的名称。…

HarmonyOS NEXT应用开发之@Provide装饰器和\@Consume装饰器:与后代组件双向同步

Provide和Consume&#xff0c;应用于与后代组件的双向数据同步&#xff0c;应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递&#xff0c;Provide和Consume摆脱参数传递机制的束缚&#xff0c;实现跨层级传递。 其中Provide装饰的变…

JVM入门到精通一篇就够了

JVM入门到精通一篇就够了 一、JVM运行时数据区域1.程序计数器2.虚拟机栈2.1 局部变量表2.2 关于局部变量表的一些思考2.3 操作数栈2.4 动态连接(Dynamic Linking)2.5 返回地址(Return Address) 3. 本地方法栈(Native Stack)4. 虚拟机堆(Heap)4.1 新生代4.2 老年代4.3 新生代对象…

来个自定义的电子木鱼吧

<!DOCTYPE html> <html><head><meta charset"utf-8"><meta name"viewport" content"widthdevice-width, initial-scale1"><title>自定义木鱼</title> </head> <body style"background-…

基于DWT(离散小波变换)的图像加密水印算法,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

软件赋能新型工业化,数智培育新质生产力——第二届软件创新发展大会蓄势待发

我国正在加快培育新质生产力&#xff0c;赋能新型工业化&#xff0c;软件作为新质生产力的基石与引擎&#xff0c;发展机遇空前。武汉是中国软件产业的重要一极&#xff0c;以其独特的地理优势和坚实的产业基础&#xff0c;正逐渐成为国内外瞩目的软件产业集聚发展高地。 武汉发…

shell 批量创建用户

代码 rootlocalhost:~ 2024-04-03 15:45:03# cat create_user.sh ######################################################################### # File Name: create_user.sh # Author: eight # Mail: 18847097110163.com # Created Time: Wed 03 Apr 2024 03:…

stm32 HAL中断GPIO——2——代码部分

1HAL中中断结构 在stm32f1XX_it.c中可以看到生成的中断函数 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3); 选中函数CtrlF 可调用出搜索框 搜索范围为整个项目 再点击Find Next 检索 可以找到可以找到如下函数 void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) {/* EXTI li…

linux 回收站机制(笔记)

Linux下回收站机制https://mp.weixin.qq.com/s/H5Y8VRcaOhFZFXzR8yQ7yg 功能 &#xff1a;设立回收站&#xff0c;并且可定时清空回收站。 一、建议将alias rm 改成别的。 比如alias rmm &#xff0c;同时修改rm -rf ~/.trash/* 改成 rmm -rf ~/.trash/* 不然影响rm 的正常使…

高压放大器高压放大测试器的操作方式讲解

HA-30K 是一台非常实用的高电压讯号放大器&#xff0c;它具有最小的体积、最轻的重量及最简易的操作&#xff0c;非常高的电压输出(30kVp-p)等优点。 本机连续输出电压量最大值达到 30kVp-p&#xff0c;还有输出过压保护&#xff0c;当输出电压≥36kVp-p 时&#xff0c;放大器…

如何在比特币上验证ZK Proofs

1. 引言 前序博客有&#xff1a; 基于BitVM的乐观 BTC bridgeBitVM&#xff1a;Bitcoin的链下合约Bitcoin Bridge&#xff1a;治愈还是诅咒&#xff1f;BitVM2&#xff1a;比特币上的无需许可验证以比特币脚本来实现SNARK VerifierClementine&#xff1a;Citrea的基于BitVM的…

kettle介绍-Step之If field value is null

If field value is null介绍 替换NULL值步骤可以将输入流中所有字段的空值进行替换&#xff0c;也可以指定一种类型下的空值进行替换&#xff0c;还可以指定一个字段下的空值进行替换 三种替换NULL模式 所有字段空值全部替换选定字段替换指定值根据数据类型替换指定值

商家转账到零钱开通不了解决方案

商家转账到零钱是什么&#xff1f; 【商家转账到零钱】可以说是【企业付款到零钱】的升级版&#xff0c;商家转账到零钱可以为商户提供同时向多个用户微信零钱转账的能力&#xff0c;支持分销返佣、佣金报酬、企业报销、企业补贴、服务款项、采购货款等自动向用户转账的场景。…

算法学习——LeetCode力扣图论篇2(1020. 飞地的数量、130. 被围绕的区域、827. 最大人工岛)

算法学习——LeetCode力扣图论篇2 1020. 飞地的数量 1020. 飞地的数量 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个大小为 m x n 的二进制矩阵 grid &#xff0c;其中 0 表示一个海洋单元格、1 表示一个陆地单元格。 一次 移动 是指从一个陆地单元格走到另一个相…

WorkPlus智能AI助理:定制化部署,拓展企业协作新境界

近年来&#xff0c;随着人工智能技术不断发展&#xff0c;智能助理在企业管理中发挥着日益重要的作用。其中&#xff0c;WorkPlus智能AI助理作为一款支持私有化部署的新一代智能助理软件&#xff0c;为企业内部协作场景带来了全新的可能性。通过结合企业自身的行业知识和应用场…

BUUCTF-Misc17

[HBNIS2018]低个头1 1.打开附件 是一个文本文档 2.键盘密码 观察文档中题目的描述&#xff0c;像键盘密码 3.得到flag 黑客帝国1 1.打开附件 一堆十六进制 2.010 Editor 新建一个十六进制文档 将文本文档导入到010Editor中 保存得到一个压缩包 3.ARCHPR 发现压缩包是一个…

Google最新论文:有限的标注和时间内提升文档信息提取的效率和准确性 Noise-Aware Training(NAT)

&#x1f4cc; 元数据概览&#xff1a; 标题&#xff1a;Noise-Aware Training of Layout-Aware Language Models作者&#xff1a;Ritesh Sarkhel, Xiaoqi Ren, Lauro Beltrao Costa, Guolong Su, Vincent Perot 等链接&#xff1a;Noise-Aware Training of Layout-Aware Lang…

深度学习平台

深度学习平台(只能有线连接校园网) 账户 yeguifeng密码 yguifengenv3 : pytorch1.9cudajupytermatplotlib pycuda: pycuda Timesformer –data_dir ./trainingVideo --category my_fire --output_dir ./output --image_size 100 --num_chain 10 --batch_size 14 --lr 0.001…