阻塞队列-PriorityBlockQueue

news2024/11/18 23:44:01

PriorityBlockingQueue

  1. 优先级队列
  2. 不满足FIFO原则
  3. 它将插入元素进行排序
  4. 排序的实现是基于数组结构实现的二叉堆排序

二叉堆

在分析优先级别队列时候,需要了解一下二叉堆是什么

  1. 二叉堆是一种完全二叉树,除了最底层外,其它层被完全填充。
  2. 二叉堆分为最小堆和最大堆
  3. 最小堆:任何一个父节点总是小于等于其任意子节点的值
  4. 最大堆:任何一个父节点宗师大于等于其任意节点的值

图解
请添加图片描述

  1. 延迟优先级队列基于优先级队列实现
  2. 优先级队列实现基于最小顶二叉堆实现
  3. 存元素时候,基于二叉堆排序
  4. 再存取数据时候,通过上下移动操作保证堆结构

源码分析

成员变量

private static final int DEFAULT_INITIAL_CAPACITY = 11; //队列(数组)默认大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//最大值,减8为了适配不同版本虚拟机
private transient Object[] queue;//存储元素的数组
private transient int size;//队列长度
private transient Comparator<? super E> comparator;//排序比较器
private final ReentrantLock lock = new ReentrantLock();//锁
private final Condition notEmpty = lock.newCondition();//挂起、唤醒线程
private transient volatile int allocationSpinLock;//标识位,代替加锁操作,提升效率
private PriorityQueue<E> q;//优先级队列,这里是为了解决序列化和反序列化使用的

注意:在PriorityBlackQueue中并没有基于PriorityQueue实现而是基于private transient Object[] queue自己实现的

添加元素

offer(E e)方法
    public boolean offer(E e) {
        //非空校验
        if (e == null)
            throw new NullPointerException();
        //获取锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        int n, cap;//队列元素数量、队列长度
        Object[] es;//存储元素的数组
        //当元素个数大于等于队列长度时候扩容
        while ((n = size) >= (cap = (es = queue).length))
            //扩容
            tryGrow(es, cap);
        //否则添加元素
        try {
            final Comparator<? super E> cmp;
            //这里是判断比较器,如果为null是应用默认的比较器
            if ((cmp = comparator) == null)
                //添加元素
                siftUpComparable(n, e, es);
            else
                //添加元素
                siftUpUsingComparator(n, e, es, cmp);
            size = n + 1;
            //唤醒生产者线程
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
        return true;
    }
tryGrow(es, cap)扩容
    //传入就旧数组和队列长度
   private void tryGrow(Object[] array, int oldCap) {
        //释放锁,基于allocationSpinLock保证线程安全
        lock.unlock(); 
        //声明一个新数组
        Object[] newArray = null;
        //这里allocationSpinLock为0证明没有线程正在扩容数组,并基于CAS将0改为1,保证其它线程不能再进行扩容
        if (allocationSpinLock == 0 &&
            ALLOCATIONSPINLOCK.compareAndSet(this, 0, 1)) {
            try {
                //如果原先数组长度小于64,扩容为原先长度一倍+1,否则扩容为原理长度的1.5倍
                int newCap = oldCap + ((oldCap < 64) ?
                                       (oldCap + 2) : // grow faster if small
                                       (oldCap >> 1));
                //如果新数组长度比最大长度大
                if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow
                    //对原先素组进行加一操作,如果加一后小于零或大于最大长度了,抛异常,超了~~
                    int minCap = oldCap + 1;
                    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
                allocationSpinLock = 0;
            }
        }
        //执行到这里证明当前线程没有进行扩容操作
        if (newArray == null) // back off if another thread is allocating
            Thread.yield();
        //加锁
        lock.lock();
        //再次判断,扩容成功了,且没有并发问题,将新数组引用复制给queue,将老数组元素复制到新数组中
        if (newArray != null && queue == array) {
            queue = newArray;
            System.arraycopy(array, 0, newArray, 0, oldCap);
        }
    }
siftUpUsingComparator添加元素的操作
    //传入元素的个数size(要放数据的索引位置),元素T,存储元素的数组queue
   private static <T> void siftUpComparable(int k, T x, Object[] es) {
        //将传入元素强转为Comparable,因此元素必须实现Comparable接口
        Comparable<? super T> key = (Comparable<? super T>) x;
        //一直循环,直到下标为0或者,找到一个比当前元素小的节点退出循环
        while (k > 0) {
            //找到当前节点的父节点
            int parent = (k - 1) >>> 1;
            //拿到父节点元素
            Object e = es[parent];
            //判断传入元素是否大于其父节点元素,如果大于直接退出while循环
            if (key.compareTo((T) e) >= 0)
                break;
            //如果不满足上面if条件,将k位置元素存放父节点元素,k修改为夫父节点下标
            //其实就是将父节点下移操作
            es[k] = e;
            k = parent;
        }
        //数组k位置存储传入元素
        es[k] = key;
    }

移除元素操作

poll()方法,关键看dequeue()方法如何保证堆结构
   public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
移除堆顶元素
    private E dequeue() {
       
        final Object[] es;//存储元素的数组
        final E result;//数组第0位置元素,即堆顶元素
        //如果队列不为空
        if ((result = (E) ((es = queue)[0])) != null) {
            final int n;//数组长度-1后的长度
            //拿到数组的最后一个元素
            final E x = (E) es[(n = --size)];
            //将堆顶元素置为null
            es[n] = null;
            //判断移除元素后队列是否为空,如果不为空,操作堆,保证堆结构
            if (n > 0) {
                final Comparator<? super E> cmp;
                //如果没有指定比较器使用默认比较器
                if ((cmp = comparator) == null)
                    siftDownComparable(0, x, es, n);
                else
                    siftDownUsingComparator(0, x, es, n, cmp);
            }
        }
        return result;
    }
siftDownComparable移除元素保证堆结构
    //传入:被取出元素的下标,数组的最后一个元素,存储元素的数组,取出元素后的数组长度
    private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {
        //将元素强转为Comparable
        Comparable<? super T> key = (Comparable<? super T>)x;
        //只遍历一个叶子树就可以保证二叉堆结构
        int half = n >>> 1;           // loop while a non-leaf
        //下移操作
        while (k < half) {
            //找到左叶子节点
            int child = (k << 1) + 1; // assume left child is least
            //坐子节点元素
            Object c = es[child];
            //右子节点
            int right = child + 1;
            //有右子节点,且左子节点大于右子节点
            if (right < n &&
                ((Comparable<? super T>) c).compareTo((T) es[right]) > 0)
                //如果左节点大于右边节点,c=右边节点,这里就是找到左右节点中较小的那个节点
                c = es[child = right];
            if (key.compareTo((T) c) <= 0)
                break;
            //将较小的节点放堆顶
            es[k] = c;
            //继续遍历下方叶子树
            k = child;
        }
        //最后将k位置存放最后索引的元素
        es[k] = key;
    }

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

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

相关文章

【AI学习指南】轻量级模型-用 Ollama 轻松玩转本地大模型

目录 探索 最小的AI模型 发现 其他轻量级模型 用 Ollama 轻松玩转本地大模型 本地大模型 Ollama 快速上手 安装 手动安装 下载ollama二进制文件 添加 Ollama 作为启动服务(推荐) 安装 CUDA 驱动程序(可选 - 适用于 Nvidia GPU) 安装 ROCm(可选 - 对于 Radeo…

飞腾2000+/64核芯片ECC功能验证

1、背景介绍 为了排查全国产飞腾计算模块的一个外场问题&#xff0c;需要验证飞腾2000/64核这个处理器的DDR控制器是否支持ECC功能&#xff0c;即在异常情况下能纠错。ECC纠错原理如下&#xff0c;目前飞腾2000/64 DDR控制器就是纠一检二&#xff1a; 2、寄存器说明 目前飞腾…

【学习过程总结】

一、二进制和十进制的转化 1、十进制转化为二进制 2、二进制转化为十进制 二进制转化为十进制这个&#xff0c;对于二进制的数&#xff0c;得从右往左看&#xff08;1《0《1《1&#xff09;。对于下面的转换过程得从下往上看 &#xff08;0、1、2、3&#xff09; 二、按位-异…

原理图----备份

从公司学到的电路图&#xff0c;做一个备份&#xff1a;

用Python做一个翻译软件,比上浏览器快100倍

简单的用Python来做一个翻译软件 开发环境 Python 3.10 Pycharm模块使用 requests -> pip install requests hashlib tkinter案例分为三部分: 1. 爬虫: 获取翻译接口, 请求获取翻译结果问题1: 接口抓包分析问题2: 请求需要写cookie问题3: 不同文本翻译, s加密参数2. 界面…

昇思25天学习打卡营第3天|基础知识-数据集Dataset

目录 环境 环境 导包 数据集加载 数据集迭代 数据集常用操作 shuffle map batch 自定义数据集 可随机访问数据集 可迭代数据集 生成器 MindSpore提供基于Pipeline的数据引擎&#xff0c;通过数据集&#xff08;Dataset&#xff09;和数据变换&#xff08;Transfor…

小模型狂飙!6家巨头争相发布小模型,Andrej Karpathy:大语言模型的尺寸竞争正在倒退...

过去一周&#xff0c;可谓是小模型战场最疯狂的一周&#xff0c;商业巨头改变赛道&#xff0c;向大模型say byebye~。 OpenAI、Apple、Mistral等“百花齐放”&#xff0c;纷纷带着自家性能优越的轻量化小模型入场。 小模型(SLM)&#xff0c;是相对于大语言模型&#xff08;LLM…

微信小游戏之三消(二)主要游戏控制方法

设计一个 game class。负责了游戏的核心控制逻辑&#xff0c;包括游戏状态管理、方块和道具的生成与效果处理&#xff0c;以及游戏的重新开始和复活流程。通过这些方法&#xff0c;脚本实现了游戏的基本玩法和用户交互。 主要游戏控制方法 gameStart()&#xff1a;开始游戏&am…

如何从网站获取表格数据

1.手动复制粘贴 最简单的方法是直接在网页上手动选择表格内容&#xff0c;然后复制粘贴到Excel或其他表格处理软件中。这种方法适用于表格较小且不经常更新的情况。 2.使用浏览器插件 有许多浏览器插件可以帮助从网页中提取表格数据&#xff0c;例如&#xff1a; -TableCapt…

liteos定时器回调时间过长造成死机问题解决思路

项目需求 原代码是稳定的&#xff0c;现我实现EMQ平台断开连接的时候&#xff0c;把HSL的模拟点位数据采集到网关&#xff0c;然后存入Flash&#xff0c;当EMQ平台连接的时候&#xff0c;把Flash里面的点位数据放在消息队列里面&#xff0c;不影响实时采集。 核心1&#xff1a…

【Linux】多线程4——线程同步/条件变量

1.Linux线程同步 1.1.同步概念与线程饥饿问题 先来理解同步的概念 什么是线程同步 在一般情况下&#xff0c;创建一个线程是不能提高程序的执行效率的&#xff0c;所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数&#xff0c;在多个线程同时对同一个内存地…

【文件fd】C++文件操作 | 详解系统调用接口文件操作 | 系统调用接口库函数

目录 1.回顾理解&引出问题 2.C文件操作 3.系统调用文件操作 3.0准备工作 3.1版本1☞open 3.2版本2☞文件权限 3.3版本3☞权限掩码 3.4版本3☞标记位传参 3.5版本4☞close 3.6版本5☞write 3.7flags选项 3.7.1 O_WRONLY | O_CREAT 3.7.2 O_WRONLY | O_CREAT …

Servlet详解(超详细)

Servlet详解 文章目录 Servlet详解一、基本概念二、Servlet的使用1、创建Servlet类2、配置Servleta. 使用web.xml配置b. 使用注解配置 3、部署Web应用4、处理HTTP请求和生成响应5、处理表单数据HTML表单Servlet 6、管理会话 三、servlet生命周期1、加载和实例化2、初始化3、 请…

Python爬虫入门02:Fiddler下载使用教程

文章目录 手机抓包全攻略&#xff1a;Fiddler 工具深度解析引言Fiddler 工具简介为什么选择 Fiddler&#xff1f; 安装与配置 Fiddler步骤一&#xff1a;下载与安装步骤二&#xff1a;配置浏览器代理步骤三&#xff1a;安装 HTTPS 证书 配置手机以使用 Fiddler步骤一&#xff1…

鸿蒙应用框架开发【OpenGL三棱椎】 NDK

OpenGL三棱椎 介绍 XComponent控件常用于相机预览流的显示和游戏画面的绘制,在HarmonyOS上&#xff0c;可以配合Native Window创建OpenGL开发环境&#xff0c;并最终将OpenGL绘制的图形显示到XComponent控件。本示例基于"Native C"模板&#xff0c;调用OpenGL(Open…

wpf中团队独立开发模块和左侧2个菜单的框架演示

此篇文章和上一篇文章wpf中开发独立模块功能和左侧1个菜单的框架演示-CSDN博客的结构是一样的&#xff0c;有1点不同的是&#xff0c;左侧有2层菜单&#xff0c;所以&#xff0c;就会更加的复杂。建议&#xff0c;先看明白上一篇的内容&#xff0c;再看这篇文章&#xff0c;否则…

CSS实现表格无限轮播

<div className{styles.tableTh}><div className{styles.thItem} style{{ width: 40% }}>报警名称</div><div className{styles.thItem} style{{ width: 35% }}>开始时间</div><div className{styles.thItem} style{{ width: 25% }}>状态&…

前端三大主流框架对比

在现代前端开发中&#xff0c;React、Vue和Angular是三大流行的框架/库。它们各自有独特的优缺点&#xff0c;适用于不同的开发需求和项目规模。下面是对这三者的详细比较&#xff1a; 一、 React 简介&#xff1a; 由Facebook开发和维护&#xff0c;是一个用于构建用户界面…

亚博科技和幻尔科技的十轴IMU在Ros2 Humble下驱动后数值无限趋于0的解决方案

在做机器人导航以及建模的时候&#xff0c;考虑到多传感器融合可能会带来更好的效果&#xff0c;于是决定使用幻尔科技的十轴IMU&#xff08;其实亚博科技与幻尔科技这块IMU的内部完全一致&#xff0c;驱动代码都完全一致&#xff09;驱动后使用以下命令输出传来的四元数等数据…