网站空间试用,pc端网站开发工具,微信开发者工具下载安装,杭州做小程序开发的公司有哪些前言
在阅读Netty源码的过程中#xff0c;我越来越相信一句话#xff1a;“Netty的源码非常好#xff0c;质量极高#xff0c;是Java中质量最高的开源项目之一”。如果认真研究#xff0c;会有一种遍地黄金的感觉。
本篇文件我将记录一下鄙人在Promise的实现类DefaultPr…前言
在阅读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