Java 入门指南:Java NIO —— Selector(选择器)

devtools/2024/9/19 0:39:44/ 标签: java, nio, 个人开发, 团队开发, java-ee, intellij-idea

NIO 的引入

在传统的 Java I/O 模型(BIO)中,I/O 操作是以阻塞的方式进行的。当一个线程执行一个 I/O 操作时,它会被阻塞直到操作完成。这种阻塞模型在处理多个并发连接时可能会导致性能瓶颈,因为需要为每个连接创建一个线程,而线程的创建和切换都是有开销的。

为了解决这个问题,在 Java1.4 版本引入了 NIO(New I/O or Non-Blocking I/O)java.nio。提供了一种基于缓冲区、选择器和非阻塞 IO 模型的 IO 处理方式。相比于之前的 BIO 模型,NIO 可以实现更高的并发、更低的延迟以及更少的资源消耗。

I/O 包和 NIO 已经很好地集成了,java.io 也已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。

![[BIO vs NIO.png]]
Java NIO 概要介绍:初识 Java NIO

使用 NIO 并不一定意味着高性能,它的性能优势主要体现在高并发和高延迟的网络环境下。当连接数较少、并发程度较低或者网络传输速度较快时,NIO 的性能并不一定优于传统的 BIO 。

Selector

NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。

通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。

由于创建和切换线程的开销很大,所以使用一个线程来处理多个事件具有更好的性能。

Selector 是 Java NIO(New I/O)库中的一个重要组件,它用于实现非阻塞 I/O 操作。它可以用于管理多个通道(如网络套接字或文件通道)的事件,从而使单个线程能够有效地处理多个通道的 I/O 操作。

使用 Selector,可以注册一个或多个通道(通道必须为非阻塞模式!),并指定感兴趣的事件类型,例如连接操作、读操作或写操作。然后,Selector 会监视这些通道上发生的事件,并且只有当感兴趣的事件发生时,才会通知我们。这样就可以在单个线程中同时处理多个通道的 I/O 事件,而无需为每个通道分配一个独立的线程。

常用方法

  1. open():打开一个选择器。

  2. select():选择一组 I/O 操作已经准备就绪的通道。该方法是阻塞的,直到至少有一个通道就绪,或者调用线程被中断。

  3. select(long timeout):选择一组 I/O 操作已经准备就绪的通道,但最多等待指定的超时时间(以毫秒为单位)。该方法是阻塞的,直到至少有一个通道就绪、超时时间到达或调用线程被中断。

  4. selectNow():选择一组 I/O 操作已经准备就绪的通道,但不会阻塞。如果没有任何通道就绪,该方法会立即返回0。

  5. selectedKeys():获取当前已经选择(就绪)进行 I/O 操作的通道的 SelectionKey 集合。

  6. wakeup():唤醒阻塞在 select()select(long timeout) 方法上的线程。

  7. keys():返回当前注册在监听器上的所有选键集合,即返回一个包含所有已经注册过的通道的 SelectionKey 集合。包括已经取消注册但还未从选键集合中移除的对象,因此需要进行有效性判断,例如使用 isValid() 先判断选键是否有效。

  8. close():关闭选择器。

SelectionKey

SelectionKeySelector 的注册对象,用于表示注册在 Selector 上的通道和感兴趣的事件。它是 NIO 中 Selector API 的核心之一。

在使用 Selector 进行事件驱动的网络编程时,每个注册到 Selector 上的通道都会关联一个 SelectionKey 对象。SelectionKey 维护了通道的状态以及感兴趣的事件。

使用 SelectionKey 可以实现基于事件驱动的处理模式,实现高效的并发网络编程。

事件类别
  • SelectionKey.OP_CONNECT:表示连接已经建立,适用于客户端的 SocketChannel。

  • SelectionKey.OP_ACCEPT:表示通道已经准备好接受新的连接请求,适用于服务端的 ServerSocketChannel。

  • SelectionKey.OP_READ:表示通道已经准备好进行读操作,即可以从通道中读取数据。

  • SelectionKey.OP_WRITE:表示通道已经准备好进行写操作,即可以向通道中写入数据。

它们在 SelectionKey 的定义如下:

java">public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;

每个事件可以被当成一个位域,从而组成事件集整数。例如:

java">int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
方法

SelectionKey 包含以下重要的属性和方法:

  1. channel():返回与此选择键关联的通道。

  2. selector():返回创建此 SelelctionKey 所属的选择器。

  3. interestOps():返回选择键当前感兴趣的操作集合,即注册时指定的操作集合。

  4. readyOps():返回通道当前已经准备就绪的操作集合。可以与interestOps() 方法的结果进行位运算判断具体的就绪事件类型。

  5. isAcceptable():判断通道是否已经准备好接受新的连接。

  6. isConnectable():判断通道是否已经准备好完成连接。

  7. isReadable():判断通道是否已经准备好进行读取操作。

  8. isWritable():判断通道是否已经准备好进行写入操作。

  9. attach(Object obj)attachment():用于在选择键上附加一个对象,以便在后续处理中获取或更新相关信息。

  10. cancel():取消该 SelectionKey 的注册,通道不再与 Selector 相关联。

  11. remove():移除指定的 SelectionKey 对象,以便下一次调用 select() 方法时不会再次触发该事件。

使用 SelectionKey 对象时,应注意它的生命周期和正确的使用方式,以避免出现资源泄漏或其他问题。可以通过选择键集合(在选择器上调用 selectedKeys() 方法)来获取就绪的选择键,并在处理完后进行适当的移除或取消注册操作。

使用流程
  1. 通过 ServerSocketChannelSocketChannelregister(Selector sel, int ops) 方法将通道注册到 Selector 上,返回一个 SelectionKey 对象。

  2. 可以通过 SelectionKey 对象获取通道、选择器、事件集合、选择键集合等信息。

  3. 通过 SelectorselectedKeys() 方法可以获取当前已经就绪的 SelectionKey 集合,可以遍历集合处理就绪事件。

注意事项
  • 一个通道只能注册到一个 Selector 上,且注册后会返回一个唯一的 SelectionKey

  • SelectionKey 的事件集合可以使用 interestOps(int ops) 方法进行更新,但更新后并不会立即生效,需要再次调用 Selectorselect() 方法。

  • 使用附件对象可以将自定义的数据与 SelectionKey 相关联,以便在事件处理时获取和使用。

  • 取消 SelectionKey 后,通道仍然保持打开状态,需要手动关闭。

使用流程

使用 Java NIO 中的选择器(Selector)时,通常可以遵循以下流程:

  1. 创建一个选择器:使用 Selector.open() 方法打开一个选择器对象。
java">Selector selector = Selector.open();
  1. 向选择器注册通道:通过调用通道的 register(Selector selector, int interestOps) 方法将通道注册到选择器上,指定对于该通道感兴趣的 I/O 事件类型(如读、写、连接等)。
java">ServerSocketChannel channel = ServerSocketChannel.open();
channel.bind(new InetSocketAddress("localhost",8888));
// 将通道设置为非阻塞模式
channel.configureBlocking(false);
channel.register(selector,Selelction.OP_ACCEPT);

一个选择器可以同时注册多个通道。

  1. 不断循环选择就绪的通道:在循环中使用选择器的 select() 方法或 select(long timeout) 方法等待通道就绪,并返回已经准备就绪的通道数量。可以根据返回值判断是否有通道就绪。
java">while(true){selector.select();// 对事件进行操作// ...
}
  1. 处理就绪的通道:通过调用选择器的 selectedKeys() 方法,获取已经准备就绪的通道的选择键集合。遍历选择键集合,可以使用 SelectionKey 对象来获取具体的就绪通道,以及就绪的I/O事件类型。
java">Set<SelelctionKey> keys =  selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
while(iter.hasNext()){Selection key = iter.next();// 处理事件// ...
}
  1. 根据事件类型进行相应的业务处理:根据通道的可操作事件类型(读、写、连接等),使用相应的方法处理相应的业务逻辑,并可能对通道进行读写操作。
java">// 如果是连接事件,accept 并注册关注 OP_READ 事件
if(key.isAcceptable()){ServerSocketChannel server = (ServerSocketChannel)key.channel();SocketChannel client = server.accept();client.configureBlocking(false);client.register(selector,SelectionKey.OP_READ);
}// 如果是读事件,读取数据并响应
else if(key.isReadable()){ServerSocket client = (ServerSocket)key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);client.read(buffer);buffer.flip();String request = new String(buffer.array(),0,buffer.limit(),StandardCharsets.UTF_8).trim();System.out.println("Client request: " + request);String response = "Response from server: ";ByteBuffer outBuffer = ByteBuffer.wrap(response.getBytes());client.write(outBuffer);
}//手动从集合中移除当前事件,避免重复处理
iter.remove();
  1. 取消通道的注册:在处理完就绪的通道后,如果不再关注该通道的 I/O 事件,可以调用选择键的 cancel() 方法取消通道的注册。
java">key.cancel();
  1. 处理其他操作(可选):根据具体需求,可能要处理一些其他操作,如再次注册通道、关闭选择器等。

  2. 关闭选择器:当不再需要使用选择器时,应调用选择器的 close() 方法关闭选择器

java">selector.close();

http://www.ppmy.cn/devtools/107149.html

相关文章

计算机毕业设计选题推荐-传统文化网站-Java/Python项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

力扣刷题--3270.求出数字答案【简单】

题目描述 给你三个 正 整数 num1 &#xff0c;num2 和 num3 。 数字 num1 &#xff0c;num2 和 num3 的数字答案 key 是一个四位数&#xff0c;定义如下&#xff1a; 一开始&#xff0c;如果有数字 少于 四位数&#xff0c;给它补 前导 0 。 答案 key 的第 i 个数位&#xf…

使用.gitignore文件忽略文件

一 .gitignore文件的作用 有些情况&#xff0c;你需要把一些文件放到Git工作目录中&#xff0c;但无需提交&#xff0c;比如本地配置&#xff0c;自动生成的文件等。.gitignore文件可以帮助你实现这样的需要。 二 .gitignore文件的创建和编辑 在git根目录或任意子目录创建名…

Vue3的多根节点组件与父子组件之间的事件继承

多根节点组件 Vue.js 3.x 版本不再对组件的 template 选项进行“唯一根节点”的限制&#xff0c;而是允许 DOM 结构具备多个根节点&#xff0c;即下列结构的全局组件在 Vue.js 3.x 版本中是允许的。 app.component(my-box,{ template: <div class"first"&…

c++ websocket简单讲解

只做简单讲解。 一.定义和原理 WebSocket 是从 HTML5 开始⽀持的⼀种⽹⻚端和服务端保持⻓连接的消息推送机制&#xff0c;传统的 web 程序都是属于 "⼀问⼀答" 的形式&#xff0c;即客⼾端给服务器发送了⼀个 HTTP 请求&#xff0c;服务器给客⼾端返回⼀个 HTTP 响…

使用ReflectionUtils进行反射操作

ReflectionUtils简化了Java反射操作的复杂性&#xff0c;提供了更加简洁和易用的API。 增强代码可读性&#xff0c;使得代码更加简洁明了&#xff0c;提高了代码的可读性和可维护性。 提高开发效率&#xff0c;通过ReflectionUtils&#xff0c;开发者可以更加快速地完成反射操作…

【达梦】“6103无效的时间类型值”解决办法

场景 使用DM数据迁移工具将excel文件里的数据导入到达梦数据库里。提示“无效的时间类型值”。 尝试 一看就是createTime等跟时间相关的字段出问题了。createTime在库里的数据类型为timeStamp。 尝试1&#xff1a;修改excel里此字段的类型&#xff0c;依旧报错。此方案失败。…

ML17_变分推断Variational Inference

1. KL散度 KL散度&#xff08;Kullback-Leibler divergence&#xff09;&#xff0c;也称为相对熵&#xff08;relative entropy&#xff09;&#xff0c;是由Solomon Kullback和Richard Leibler在1951年引入的一种衡量两个概率分布之间差异的方法。KL散度不是一种距离度量&am…

ThinkPHP A表和B表一对多关联,根据B表中符合条件记录的某个字段的值对A表数据进行排序。

A表是简历表结构如下 CREATE TABLE fa_resume (user_id int(11) NOT NULL,name varchar(255) DEFAULT NULL COMMENT 姓名,sex enum(1,0) DEFAULT 1 COMMENT 性别:1男,0女,birthday date DEFAULT NULL COMMENT 出生年月,shenfen enum(1,2) DEFAULT 1 COMMENT 身份:1职场人,2学生…

深度学习(二)

CuDNN&#xff08;CUDA Deep Neural Network library&#xff09;是NVIDIA为加速深度学习计算而开发的高性能GPU加速库&#xff0c;专门优化了深度神经网络&#xff08;DNN&#xff09;的常见操作&#xff0c;如卷积、池化、归一化和激活函数等。CuDNN的主要作用是通过利用GPU的…

Docker镜像中的源替换为国内源

Docker镜像中的源替换为国内源 介绍示例 介绍 使用Dockerfile构建Containers&#xff0c;通常国内网络更新安装包会有网络问题。本文以python:3.11.7-slim-bookworm镜像为例&#xff0c;实现替换镜像源。 示例 要将基于 python:3.11.7-slim-bookworm 的 Docker 镜像的源替换…

jquery下载的例子如何应用到vue中

参考测试圈相亲平台开发流程&#xff08;4&#xff09;&#xff1a;选个漂亮的首页 (qq.com) 下载的文件夹解压到v_love项目的pubilc下的static文件夹内&#xff0c;这里放的都是我们的静态资源。 打开文件夹内的index.html&#xff0c;我们先确定下它是不是我们要的东西&…

代码随想录第50天|图论

Carl.98所有可达路径 代码随想录​​​​​​ 这道题属于深搜入门题&#xff0c;需要规范图论题写法&#xff1a;存储图&#xff0c;处理图&#xff0c;输出图 图的存储 邻接矩阵 有n个结点申请【n1】*【n1】大小的矩阵初始化边邻接表&#xff1a;数组链表 n个结点申请【n1】大…

如何在MySQL中实现乐观锁

在MySQL中实现乐观锁通常不依赖于数据库内建的锁机制&#xff08;如行级锁、表级锁等&#xff09;&#xff0c; 而是通过在应用程序层面来控制。乐观锁的基本思想是在数据表中增加一个版本号 &#xff08;version&#xff09;或时间戳&#xff08;timestamp&#xff09;字段&am…

Golang | Leetcode Golang题解之第383题赎金信

题目&#xff1a; 题解&#xff1a; func canConstruct(ransomNote, magazine string) bool {if len(ransomNote) > len(magazine) {return false}cnt : [26]int{}for _, ch : range magazine {cnt[ch-a]}for _, ch : range ransomNote {cnt[ch-a]--if cnt[ch-a] < 0 {r…

C语言 strlen求字符串长度

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; C 标准库 - <string.h…

关于zotero无法识别拖入的pdf和caj的题录信息

一、首先要安装好茉莉花插件 1、点击链接&#xff0c;进入Zotero 插件商店&#xff0c; Zotero 插件商店 | Zotero 中文社区 (zotero-chinese.com) 搜索&#xff1a;Jasminum&#xff0c;选择好版本&#xff0c;点击下载 2、 下载好后&#xff0c;点击“工具”&#xff0c;…

【主机入侵检测】Wazuh解码器之JSON解码器

前言 Wazuh 是一个开源的安全平台&#xff0c;它使用解码器&#xff08;decoders&#xff09;来从接收到的日志消息中提取信息。解码器将日志信息分割成字段&#xff0c;以便进行分析。Wazuh 解码器使用 XML 语法&#xff0c;允许用户指定日志数据应该如何被解析和规范化。解码…

给ui添加 更换material 脚本

//比如置灰&#xff0c;等方便的功能&#xff0c;用此脚本 public class UIMaterialEffect : MonoBehaviour { [SerializeField] private Material mat; [SerializeField] private Graphic graphic; #if UNITY_EDITOR [ContextMenu("En…

AI学会“视听”新语言,人大北邮上海AI Lab引领多模态理解革命 | ECCV2024亮点

你是否想过&#xff0c;AI是如何“理解”我们这个多彩世界的呢&#xff1f; 最近&#xff0c;一项由中国人民大学高瓴GeWu-Lab、北京邮电大学、上海AI Lab等机构联合研究的成果&#xff0c;为AI的“感官”升级提供了一种新思路。 这项研究被收录于即将召开的计算机视觉顶级会议…