Netty源码解析之异步处理(二):盛赞Promise中的集合设计
前言
在阅读Netty源码的过程中,我越来越相信一句话:“Netty的源码非常好,质量极高,是Java中质量最高的开源项目之一”。如果认真研究,会有一种遍地黄金的感觉。
本篇文件我将记录一下鄙人在Promise的实现类DefaultPromise中发现的一块黄金:即用来存储监听器的集合的设计。
问题引入
接上文《Netty源码解析之异步处理(一):Promise系列的源码与实现原理》,在使用Promise时,可以往Promise里面加多个监听器。那么在Promise中改用什么集合来保存已经添加的监听器呢?
我认为大部分程序员都会使用一个Set或List等集合来存储,Netty则认为这些统统不合适,使用了自定义的DefaultFutureListeners集合来存储。
Promise中的集合设计
奇怪的listeners属性
在DefaultPromise源码中,用来存储监听器的属性是一个Object类型的listeners。乍看会觉得很奇怪,因为Promise中的监听器可能不止一个,用一个非集合的listeners如何存储?
DefaultPromise源码中的listeners
//用来存储添加到Promise中的监听器
private Object listeners;
单个监听器添加部分的源码
为了解答上面的疑问,需要看下DefaultPromise中添加单个监听器部分的源码,位于addListener0(GenericFutureListener listener) 方法中。
private void addListener0(GenericFutureListener<? extends Future<? super V>> listener) {//当listeners == null时,表示是第一次添加监听器if (listeners == null) {listeners = listener;//等到第三次添加时,listeners已经是DefaultFutureListeners对象//因此走了这一步} else if (listeners instanceof DefaultFutureListeners) {((DefaultFutureListeners) listeners).add(listener);//当listeners != null,表示已经不是第一次添加//如果是第二次添加的话,listeners此时是一个监听器GenericFutureListener的实例,//因此,第二次添加的话,走这一步,创建DefaultFutureListeners实例赋值给listeners} else {listeners = new DefaultFutureListeners((GenericFutureListener<?>) listeners, listener);}}
从上面的源码中,我们可以看出,添加单个监听器分为三种方式:
1、第一次添加监听器时,直接把监听器,即GenericFutureListener类型的实例赋值给DefaultPromise中用来存储监听器的listeners属性。
2、第二次添加监听器时,创建了DefaultFutureListeners集合的对象,并且将两次添加的监听器作为参数传递。
然后,我们进入DefaultFutureListeners的构造方法。
DefaultFutureListeners(GenericFutureListener<? extends Future<?>> first, GenericFutureListener<? extends Future<?>> second) {//创建一个长度为2的数组listeners = new GenericFutureListener[2];//将第一次和第二次添加的两个监听器存入数组中listeners[0] = first;listeners[1] = second;//数组长度为2size = 2;//如果添加的监听器是进度监听器,progressiveSize自增1if (first instanceof GenericProgressiveFutureListener) {progressiveSize ++;}if (second instanceof GenericProgressiveFutureListener) {progressiveSize ++;}}
可以发现,在DefaultFutureListeners的构造方法中,创建一个长度为2的数组listeners,然后将第一次和第二次添加的两个监听器存入数组中。这时候,可以说两个监听器已经存储在DefaultFutureListeners集合中。
3、等到第三次或第三次以后添加时,调用DefaultFutureListeners的add方法将监听器存入集合。
在DefaultFutureListeners的add方法中,进行了检查数组长度和监听器插入数组等操作,没什么特别的。
public void add(GenericFutureListener<? extends Future<?>> l) {GenericFutureListener<? extends Future<?>>[] listeners = this.listeners;//获取当前集合中元素的数量final int size = this.size;//如果当前集合中元素的数量等于数组长度//说明本次添加时,数组长度就不足,因此数组需要扩容if (size == listeners.length) {//数组扩容,先用左移位将新数组长度设为原数组长度的两倍//然后使用数组拷贝的方式得到新数组this.listeners = listeners = Arrays.copyOf(listeners, size << 1);}//将监听器插入数组中listeners[size] = l;//集合中元素数量增加1this.size = size + 1;//如果本次添加的是进度监听器,progressiveSize也自增1if (l instanceof GenericProgressiveFutureListener) {progressiveSize ++;}}
Promise中集合设计的思考
为什么要这么设计?
刚开始我觉得非常奇怪,
1、为什么不直接把DefaultPromise源码中的listeners属性设为一个ArrayList类型的集合,而是要兜了一圈才用集合?
2、为什么DefaultFutureListeners创建后,其内部的数组长度只有2?多给点初始长度不是能避免数组扩容吗?
后来我在不断地阅读Netty源码时发现,在几乎全部的Promise实际使用场景中,添加的监听器数量很少,同一个Promise在大部分情况下只用了1个监听器,很少数情况下用了2个监听器,用到3个监听器的情况从未见过。
基于这种实际情况,如果刚开始就创建一个集合,甚至给集合中的数组分配一定的初始长度的话,在性能和存储空间上都是浪费!因为在大部分场景下一个Promise只包含1个监听器,所以直接把这一个监听器赋值给listeners属性是最好的选择。如果遇到了极少数的需要包含2个监听器的情况,那也只创建一个长度为2的数组来保存,因为监听器再多的情况几乎没有,这样避免空间浪费。
这种设计和编码方式叫做“启发式编程”。
使用栈可不可以?
我也想过Promise的监听器使用栈这种数据结构来存储是否可以,这样的话我们只要在监听器GenericFutureListener中定义一个next属性,用来指向下一个监听器即可,编码更加简洁和方便。
我认为可以,但是性能不如数组。因为在Promise的源码中,存储的监听器最多的使用场景就是遍历全部然后触发。因为数组在内存中是连续的,正好可以利用计算机的局部性原理,能让CPU缓存把本身就很小的数组全部读入,进而能以最快的速度进行遍历。而栈使用的是链表结构,链表的节点是分散在堆空间里面的,很难使用到CPU缓存。
数组与CPU缓存的详细关联请参考:https://www.cnblogs.com/ajuanabc/archive/2009/03/28/2462628.html
相关文章:
Netty源码解析之异步处理(二):盛赞Promise中的集合设计
前言 在阅读Netty源码的过程中,我越来越相信一句话:“Netty的源码非常好,质量极高,是Java中质量最高的开源项目之一”。如果认真研究,会有一种遍地黄金的感觉。 本篇文件我将记录一下鄙人在Promise的实现类DefaultPr…...
NetworkX布局算法:nx.spring_layout
诸神缄默不语-个人CSDN博文目录 官方文档:https://networkx.org/documentation/stable/reference/generated/networkx.drawing.layout.spring_layout.html 和nx.fruchterman_reingold_layout()等价。 这个函数主要是为了在可视化NetworkX图时设置节点分布布局的&…...
Navicat导入海量Excel数据到数据库(简易介绍)
目录 前言正文 前言 此处主要作为科普帖进行记录 原先Java处理海量数据的导入时,由于接口超时,数据处理不过来,后续转为Navicat Navicat 是一款功能强大的数据库管理工具,支持多种数据库系统(如 MySQL、PostgreSQL、…...
LeetCodehot100 力扣热题100 二叉树展开为链表
代码思路 目标: 将二叉树展平(flatten)为一个单链表。展平后的链表应该按照前序遍历的顺序排列节点,即: • 节点的左子树指针设置为 nullptr。 • 节点的右子树指针指向下一个节点。 代码注释及思路 class Solution…...
2.14学习总结
#include <stdio.h> #include <stdlib.h> #include <math.h>#define MAX_N 32767// 二分查找最接近目标值的元素 int binarySearch(int* arr, int left, int right, int target) {while (left < right) {int mid left (right - left) / 2;if (arr[mid] …...
在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档教程
既然我们已经在本地部署了DeepSeek,肯定希望能够利用本地的模型对自己软件开发、办公文档进行优化使用,接下来就先在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档的教程奉上。 前提: (1)已经部署好了DeepSeek,可以看我的文章:个人windows电脑上安装DeepSe…...
zola + github page,用 workflows 部署
之前的Zola都是本地build之后,再push到github上,这种方式很明显的弊端就是只能在本地编辑,而不能通过github编辑,再pull到本地,缺乏了灵活性。因此将zola用workflows来部署。 repo地址:https://github.com/…...
【科技革命】颠覆性力量与社会伦理的再平衡
目录 2025年科技革命:颠覆性力量与社会伦理的再平衡目录技术突破全景图认知智能的范式转移量子霸权实现路径生物编程技术革命能源结构重构工程 产业生态链重构医疗健康新范式教育系统智能进化金融基础设施变革制造范式革命 科技伦理与文明演进 2025年科技革命&#…...
UIView 与 CALayer 的联系和区别
今天说一下UIView 与 CALayer 一、UIView 和 CALayer 的关系 在 iOS 开发中,UIView 是用户界面的基础,它负责处理用户交互和绘制内容,而 CALayer 是 UIView 内部用于显示内容的核心图层(Layer)。每个 UIView 内部都有…...
Jenkins 新建配置 Freestyle project 任务 六
Jenkins 新建配置 Freestyle project 任务 六 一、新建任务 在 Jenkins 界面 点击 New Item 点击 Apply 点击 Save 回到任务主界面 二、General 点击左侧 Configure Description:任务描述 勾选 Discard old builds Discard old builds:控制何时…...
深入解析A2DP v1.4协议:蓝牙高质量音频传输的技术与实现
1. A2DP概述 A2DP(Advanced Audio Distribution Profile)是一种高质量音频流媒体协议,旨在实现高质量音频内容的分发,通常用于通过蓝牙设备传输音频数据,例如将音乐从便携式播放器传输到耳机或扬声器。与传统的蓝牙语…...
mybatis-plus逆向code generator pgsql实践
mybatis-plus逆向code generator pgsql实践 环境准备重要工具的版本供参考pom依赖待逆向的SQL 配置文件CodeGenerator配置类配置类说明 环境准备 重要工具的版本 jdk1.8.0_131springboot 2.7.6mybatis-plus 3.5.7pgsql 14.15 供参考pom依赖 <?xml version"1.0&quo…...
Android Studio:RxBus结合ICompositeSubscription使用
我现在想用 RxBus 来发布和订阅事件,同时使用 ICompositeSubscription 来管理订阅。跟前一个博客的区别在于,事件流的产生方式不同,更加得全面。 目标 使用 RxBus 发布事件。使用 ICompositeSubscription 来管理订阅。在 Activity 中创建订…...
微软AutoGen高级功能——Magentic-One
介绍 大家好,博主又来给大家分享知识了,这次给大家分享的内容是微软AutoGen框架的高级功能Magentic-One。那么它是用来做什么的或它又是什么功能呢,我们直接进入正题。 Magentic-One Magnetic-One是一个通用型多智能体系统,用于…...
redis cluster测试
集群节点信息这时候停掉一个master 172.30.60.31 从集群信息集中我们可以看到172.30.60.31的slave是172.30.60.41,查看41的日志,发现他成为了新的master 这时候我们在将172.30.60.41也杀死,会发现集群异常了 尝试把172.30.60.31启动ÿ…...
【ARM】JTAG接口介绍
1、 文档目标 对 JTAG 接口有更多的认识,在遇到关于 JTAG 接口问题时有一些排查的思路。 2、 问题场景 在使用调试器过程时,免不了要接触到 JTAG 接口,当出现连接不上时,就不知道从哪来进行排查。 3、软硬件环境 1 软件版本&am…...
处理项目中存在多个版本的jsqlparser依赖
异常提示 Correct the classpath of your application so that it contains a single, compatible version of net.sf.jsqlparser.statement.select.SelectExpressionIte实际问题 原因:项目中同时使用了 mybatis-plus 和 pagehelper,两者都用到了 jsqlpa…...
部署 DeepSeek R1各个版本所需硬件配置清单
DeepSeek-R1 通过其卓越的推理性能和灵活的训练机制,在 2025 年的春节期间受到了广泛关注。 DeepSeek-R1 是一款高性能的 AI 推理模型,主要通过强化学习技术来增强模型在复杂任务场景下的推理能力。 在本地部署 DeepSeek-R1 时,尤其是完整的…...
数据结构:Map Set(一)
目录 一、搜索树 1、概念 2、查找 3、插入 4、删除 二、搜索 1、概念及场景 2、模型 (1)纯key模型 (2)Key-Value模型 三、Map的使用 1、什么是Map? 2、Map的常用方法 (1)V put(K …...
zabbix 监控系统 配置钉钉告警
步骤1:创建钉钉群 步骤2:创建机器人 点击群设置 然后下划选择机器人。 点击添加机器人 选择自定义机器人 点击添加 1、设置机器人的名字和群组 2、设置自定义关键字 zabbix 告警 报警 恢复 3、点击我已阅读并同意 4、点击完成 生成webhook 链接 注…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
