PriorityBlockingQueue的tryGrow方法

前言:

最近看PriorityBlockingQueue这个类的过程中,对扩容方法产生了一些困惑,特此记录下自己思索的过程。

PriorityBlockingQueue:

PriorityBlockingQueue 是带优先级的无界阻塞队列,每次出队都返回优先级最高或者最低的元素。其内部是使用平衡二叉树堆实现的,所以直接遍历队列元素不保证有序。默认使用对象的 ompareTo 方法提供比较规则,如果你需要自定义比较规则则可以自定义 comparators。

构造方法:

构造方法有3个分别是:

无参:默认容量11

public PriorityBlockingQueue() {this(DEFAULT_INITIAL_CAPACITY, null);}

有参有三个:分别是指定初始容量或追加一个比较器

 public PriorityBlockingQueue(int initialCapacity) {this(initialCapacity, null);}
public PriorityBlockingQueue(int initialCapacity,Comparator<? super E> comparator) {if (initialCapacity < 1)throw new IllegalArgumentException();this.lock = new ReentrantLock();this.notEmpty = lock.newCondition();this.comparator = comparator;this.queue = new Object[initialCapacity];}

 有参还有一个是接收一个集合

 public PriorityBlockingQueue(Collection<? extends E> c) {this.lock = new ReentrantLock();this.notEmpty = lock.newCondition();boolean heapify = true; // true if not known to be in heap orderboolean screen = true;  // true if must screen for nullsif (c instanceof SortedSet<?>) {SortedSet<? extends E> ss = (SortedSet<? extends E>) c;this.comparator = (Comparator<? super E>) ss.comparator();heapify = false;}else if (c instanceof PriorityBlockingQueue<?>) {PriorityBlockingQueue<? extends E> pq =(PriorityBlockingQueue<? extends E>) c;this.comparator = (Comparator<? super E>) pq.comparator();screen = false;if (pq.getClass() == PriorityBlockingQueue.class) // exact matchheapify = false;}Object[] a = c.toArray();int n = a.length;// If c.toArray incorrectly doesn't return Object[], copy it.if (a.getClass() != Object[].class)a = Arrays.copyOf(a, n, Object[].class);if (screen && (n == 1 || this.comparator != null)) {for (int i = 0; i < n; ++i)if (a[i] == null)throw new NullPointerException();}this.queue = a;this.size = n;if (heapify)heapify();}

常用方法:

1.offer 操作

在队列中插入一个元素,由于是无界队列,因此一直返回 true 。会返回最小的元素。

public boolean offer(E e) {//元素喂null时,会抛空指针异常if (e == null)throw new NullPointerException();//获取锁对象final ReentrantLock lock = this.lock;lock.lock();int n, cap;Object[] array;//size大于等于队列的长度while ((n = size) >= (cap = (array = queue).length))//尝试扩容tryGrow(array, cap);try {Comparator<? super E> cmp = comparator;//上浮排序if (cmp == null)//默认比较器siftUpComparable(n, e, array); else//自定义比较器siftUpUsingComparator(n, e, array, cmp);//size+1size = n + 1;//唤醒因take的阻塞线程notEmpty.signal();} finally {lock.unlock();}return true;}

扩容方法:
 private void tryGrow(Object[] array, int oldCap) {//扩容的时候先释放锁 保证在此过程其他线程可以入队出队,增加并发性能lock.unlock(); // must release and then re-acquire main lockObject[] newArray = null;//先判断是否处于扩容状态中,并CAS更新扩容状态 更新成功的线程进行扩容if (allocationSpinLock == 0 &&UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,0, 1)) {try {//判断旧的容量如果小于64,那么新的容量就等于oldCap*2+2,否则为oldCap的1.5倍int newCap = oldCap + ((oldCap < 64) ?(oldCap + 2) : // grow faster if small(oldCap >> 1));/*此处判断是否超过最大容量MAX_ARRAY_SIZE,MAX_ARRAY_SIZE为Integer.MAX_VALUE-8, 疑问一? 当oldCap过大时,newCap会发生数据溢出成为负数时小于0 为什么不判断该情况?*/if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow//此处说明 新的容量超过了最大值,进行最小扩容 int minCap = oldCap + 1;/*此处判断 minCap的合法性 大于0 且必须小于最大值 否则抛出异常疑问二? 此处为什么要判断minCap<0? min小于0的情况只有                    oldCap=Integer.MAX_VALUE才可能发生,但是oldCap大于MAX_ARRAY_SIZE(Integer.MAX_VALUE-8)时就会抛异常,那么它如何来的?*/if (minCap < 0 || minCap > MAX_ARRAY_SIZE)throw new OutOfMemoryError();//走此处说明最小扩容值合法,直接扩容为最大容量newCap = MAX_ARRAY_SIZE;}//如果新的容量>旧的容量 并且数组没有发生改变 进行扩容 //疑问三:此处为何要判断引用一致问题if (newCap > oldCap && queue == array)newArray = new Object[newCap];} finally {//无论新容量成功还是失败都要修改扩容状态allocationSpinLock = 0;}}
//CAS不成功的线程,会直接到此处,让出CPU时间片,尽量让扩容线程优先(但这得不到保证)if (newArray == null) // back off if another thread is allocatingThread.yield();//获取锁,进行扩容 理论上可以是上面CAS失败的线程获取到的lock.lock();/*如果没有newArray=null 说明扩容还未完成 会重新调用扩容方法,重走流程(外出while循环扩容),并再次调用Thread.yield(),给扩容线程让出cpu片 疑问四?为什么要判断 queue == array呢,按理说已经抢到锁了,且走到这里肯定也是完成了newArray,为什么不直接扩容而要再次判断引用一致?  
*/if (newArray != null && queue == array) {//复制当前数组元素到新数组queue = newArray;System.arraycopy(array, 0, newArray, 0, oldCap);}}
疑问一:

当oldCap过大时,newCap会发生数据溢出成为负数时小于0 为什么不判断该情况?

当oldCap越接近Inter.MAX_VALUE时,扩容时newCap必定是负数,但是JDK团队很巧妙的用newCap - MAX_ARRAY_SIZE > 0去判断。MAX_ARRAY_SIZE为Inter.MAX_VALUE-8。当一个负数减去一个更大的正数时,会成为一个更大的负数,负数同样会发生数据溢出,溢出后会变为正数最大数。只要这个负数<-8,那么再减去MAX_ARRAY_SIZE就一定会溢出变为正数。此处判断肯定会大于0.说明到达newCap已经超过最大界限了。当newCap为正数时,若判断通过,同样说明超过了MAX_ARRAY_SIZE。如果newCap - MAX_ARRAY_SIZE=0时,意味着newCap已经是MAX_ARRAY_SIZE,可以直接进行最大扩容。而newCap - MAX_ARRAY_SIZE<0的情况说明还远没到最大界限的判断,也可以直接扩容。

 疑问二:

此处为什么要判断minCap<0? min小于0的情况只有oldCap=Integer.MAX_VALUE才可能发生,但是oldCap大于MAX_ARRAY_SIZE(Integer.MAX_VALUE-8)时就会抛异常,那么它如何来的?

这个问题虽然很简单,但其实困扰了我好几天。后来偶然间想到会不会是构造方法,然后去验证一下,结果答案就是构造方法,构造方法并未对initialCapacity有限制,只判断了不能为负数的情况。也就是说可以初始容量可以为MAX_VALUE,所以这就是此处有这个判断存在的理由。这个事情也侧面说明解决问题不应只局限于当前的眼光,有时要从整体方面去看待一个事物,不能一叶障目。

 疑问三:

此处为何要判断引用一致问题?

这块其实比较抽象,计算newCap时,由于没有锁可能有多个线程都在进行扩容,若有线程已经计算完,并获取锁成功完成了引用替换,说明已经在扩容了,那么后来的线程到此处就不用再进行扩容了。

疑问四:

为什么要判断 queue == array呢,按理说已经抢到锁了,且走到这里肯定也是完成了newArray,为什么不直接扩容而要再次判断引用一致? 

这块其实也想了挺长时间,也走了一些弯路。答案其实和疑问三一样。简而言之,多个线程都在进行扩容,若当前线程计算完newCap创建数组的时候,已有其他线程完成并抢到锁,进行引用替换,数组复制。释放锁后,被当前线程抢到,那么当前线程其实就没要了多此一举了。一句话总结就是保证多线程扩容时,只有一个线程能扩容成功。

2.put 操作

内部调用的是 offer 操作 ,由于是无界队列,所以不需要阻塞。查阅资料发现有人对这个方法的阻塞有错误的理解,明明上锁了,为什么说说不阻塞。和其他阻塞队列相比,其他阻塞队列大都是有界队列,满了的话,需要先等其他线程调用take或poll方法,等队列有空位了,再去入队。这个由于是无界队列,直接扩容,且扩容时也不上锁。所以JDK团队才会在这个方法的源码加一句never need to block注释吧。

3.poll和take

两者都差不多,都是调用出列方法,所以只贴一个。

public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();E result;try {//此处判断对列中是否还有元素,若没有会阻塞while ( (result = dequeue()) == null)notEmpty.await();} finally {lock.unlock();}return result;}
  private E dequeue() {//判断队列是否为空int n = size - 1;if (n < 0)return null;else {Object[] array = queue;//获取第一个元素E result = (E) array[0];//获取最后一个元素E x = (E) array[n];array[n] = null;//下沉算法重排序Comparator<? super E> cmp = comparator;if (cmp == null)siftDownComparable(0, x, array, n);elsesiftDownUsingComparator(0, x, array, n, cmp);//修改sizesize = n;return result;}}
4.remove
 public boolean remove(Object o) {final ReentrantLock lock = this.lock;
//上锁lock.lock();try {//获取该元素下标int i = indexOf(o);if (i == -1)//找不到返回falsereturn false;//删除对应下标元素removeAt(i);return true;} finally {lock.unlock();}}
private void removeAt(int i) {Object[] array = queue;//判断队列元素是否1个int n = size - 1;if (n == i) // removed last elementarray[i] = null;else {//获取该位置元素E moved = (E) array[n];//将下标置为nullarray[n] = null;//下沉算法 保持堆一致Comparator<? super E> cmp = comparator;if (cmp == null)siftDownComparable(i, moved, array, n);elsesiftDownUsingComparator(i, moved, array, n, cmp);//这种情况说明移动过去后,根本没有下沉(如果有下沉,i处肯定会变成一个比moved小的数)if (array[i] == moved) {if (cmp == null)siftUpComparable(i, moved, array); //上移elsesiftUpUsingComparator(i, moved, array, cmp);}}size = n;}
5.iterator

和JUC的大部分一样,为了并发性能,都是弱一致性迭代。

其他

这些都是自己的一些思考,也有参考过书或是网上其他的文章,纯属个人理解,如果有其他的思路欢迎一起讨论。一些其他的方法如上浮和下沉有点抽象,本人数据结构也不太好,只看了个大概,由于自己都没参透,就不再贴出误人子弟了。

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

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

相关文章

pdmodel从动态模型转成静态onnx

1.下载项目 git clone https://github.com/jiangjiajun/PaddleUtils.git 2.新建两个新的文件夹 第一个文件夹放两个必要文件 第二个文件夹可以设置为空&#xff0c;用来存放转换后的模型 如图&#xff1a; 3.在终端运行 python paddle/paddle_infer_shape.py --model_dir …

GMSSL之ZUC256算法

GmSSL介绍 GmSSL是一个开源的密码工具箱&#xff0c;支持SM2/SM3/SM4/SM9/ZUC等国密(国家商用密码)算法。 从 GmSSL 官网处得到的下载链接为 GitHub - guanzhi/GmSSL: 支持国密SM2/SM3/SM4/SM9/SSL的密码工具箱 GmSSL的下载编译如下&#xff1a; # git clone https://g…

谷粒商城【成神路】-【4】——分类维护

目录 1.删除功能的实现 2.新增功能的实现 3.修改功能的实现 4.拖拽功能 1.删除功能的实现 1.1逻辑删除 逻辑删除&#xff1a;不删除数据库中真实的数据&#xff0c;用指定字段&#xff0c;显示的表示是否删除 1.在application.yml中加入配置 mybatis-plus:global-config:…

【PostgreSQL内核学习(二十五) —— (DBMS存储空间管理)】

DBMS存储空间管理 概述块&#xff08;或页面&#xff09;PageHeaderData 结构体HeapTupleHeaderData 结构 表空间表空间的作用&#xff1a;表空间和数据库关系表空间执行案例 补充 —— 模式&#xff08;Schema&#xff09; 声明&#xff1a;本文的部分内容参考了他人的文章。在…

【HarmonyOS】鸿蒙开发之自定义组件——第3.7章

自定义构建函数 (适合内部页面的封装&#xff0c;更加合适)(构建页面) 案例: 自定义组件文件 Index.ets //全局自定义构建函数写法 Builder function item1(){Row({space:10}){Text("我是自定义构建函数")} }Component export struct Index{build(){Column(){item…

【红包封面发放+微信红包封面制作教程】小黑猫祝大家小年快乐~

今年终于成功获得了微信红包封面~是我们家的小黑猫&#xff0c;嘿嘿。 封面获取方式 一共还有600份&#xff0c;数量有限&#xff0c;大家想要的话请关注文末的公众号&#xff0c;访问红包封面相关的推文获取~ 平时公众号主要发布一些技术类工具知识&#xff0c;希望能帮到大…

Vue2+ElementUI 弹窗全局拖拽 支持放大缩小

拖拽组件 dialogDrag.vue <template><div></div> </template> <script>export default {name: dialogDrag,data() {return {originalWidth: null,originalHeight: null}},created() {this.$nextTick(()>{this.dialogDrag()})},mounted() {}…

cesium-场景出图场景截屏导出图片或pdf

cesium把当前的场景截图&#xff0c;下载图片或pdf 安装 npm install canvas2image --save npm i jspdf -S 如果安装的插件Canvas2Image不好用&#xff0c;可自建js Canvas2Image.js /*** covert canvas to image* and save the image file*/ const Canvas2Image (function…

Linux下的线程操作

一、多线程的创建于退出 1. pthread_create(线程的创建) pthread_create 是 POSIX 线程库中的函数&#xff0c;用于创建一个新的线程。 函数原型如下&#xff1a; int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void…

无人机激光雷达标定板

机载激光雷达标定板是用于校准和验证机载激光雷达系统的设备。由于机载激光雷达系统在测量地形、建筑物和植被等方面具有广泛的应用&#xff0c;因此标定板的使用对于确保测量结果的准确性和可靠性至关重要。 标定板通常由高反射率的材料制成&#xff0c;如镀金的玻璃或陶瓷&am…

计算机网络实验五

目录 实验五 路由器基本配置 1、实验目的 2、实验设备 3、网络拓扑及IP地址分配 4、实验过程 &#xff08;1&#xff09;路由器设备名称的配置 &#xff08;2&#xff09;路由器每日提示信息配置 &#xff08;3&#xff09;路由器端口的IP地址配置 &#xff08;4&…

目标检测YOLO实战应用案例100讲-【目标检测】Halcon(工具应用篇)

目录 Image、Regiong、XLD相关知识 一 读取的3种方式: 二 图像变量Region 三 图型变量

Docker 阿里云镜像仓库CR使用实践

一、使用容器镜像&#xff0c;查看镜像&#xff0c;创建&#xff0c;推送&#xff0c;拉取阿里云镜像 CR镜像管理&#xff08;阿里云容器镜像服务&#xff08;Container Registry&#xff09;&#xff09; 登录实例 未创建的镜像名称也可以push、docker的私有仓库需要提起创建…

微信小程序新手入门教程二:认识JSON配置文件

在上一篇我们介绍了微信小程序的注册和基本使用方式&#xff0c;并且写出了一个简单的页面&#xff0c;但是依然没有解释目录中的各种.json文件是做什么的。这篇我们就来认识一下各种JSON配置文件及其配置项。 一 认识JSON 首先先来认识一下JSON是什么。 JSON 指的是 JavaScri…

航道大数据应用专项研究报告(附下载)

总体目标 充分认识航道大数据对行业治理的重要性和必要性&#xff0c;航道大数据的开发和利用是建设智慧航道的基础。基于大数据的航道管理体系&#xff0c;实现了现有数据的梳理和汇聚&#xff0c;跨部门数据的交换和整合&#xff0c;建立了数据关联和深度学习的模型机制&…

网络异常案例六_IP冲突

问题现象 同一个局域网下&#xff0c;一个路由器带几十台终端设备&#xff0c;存在终端设备获取到了相同IP的场景。该路由器也是DHCP Server。 有两个设备终端&#xff0c;都显示获取到了192.168.11.177这个ip。 抓包分析 抓包过程中&#xff0c;看到的一些问题。 ps&#x…

​(三)hadoop之hive的搭建1

下载 访问官方网站https://hive.apache.org/ 点击downloads 点击Download a release now! 点击https://dlcdn.apache.org/hive/ 选择最新的稳定版 复制最新的url 在linux执行下载命令 wget https://dlcdn.apache.org/hive/hive-3.1.3/apache-hive-3.1.3-bin.tar.gz 2.解压…

第十一篇【传奇开心果系列】Python的OpenCV技术点案例示例:三维重建

传奇开心果短博文系列 系列短博文目录Python的OpenCV技术点案例示例系列 短博文目录一、前言二、OpenCV三维重建介绍三、基于区域的SGBM示例代码四、BM&#xff08;Block Matching&#xff09;算法介绍和示例代码五、基于能量最小化的GC&#xff08;Graph Cut&#xff09;算法介…

从编程中理解:大脑的动态更新与信息处理

在深入探讨大脑动态更新与信息处理的过程中,我们可以结合编程语言C#的逻辑来构建一个以金庸武侠世界为背景的故事。设想张无忌在《倚天屠龙记》中学习和掌握九阳真经的过程,我们可以将其类比为一种内存管理和知识积累系统。以下是一个简化版但富含叙事元素的Unity C#脚本示例…

【云计算】Openstack配置Redis服务—一主二从三哨兵模式

Redis一主二从三哨兵模式 hostnamectl set-hostname //修改主机名将提供的Redis安装文件下载redis-3.2.12.tar.gz到三台虚拟机中&#xff0c;解压到/opt目录中&#xff0c;并配置yum源使用本地目录&#xff0c;命令如下&#xff08;三台虚拟机操作一致&#xff0c;以redis1主…