Java多线程篇(11)——BlockingQueue(优先级阻塞,延迟队列)

news2024/10/5 17:25:49

文章目录

  • 1、PriorityBlockingQueue
  • 2、DelayQueue

1、PriorityBlockingQueue

优先级阻塞队列就是在优先级队列的基础上增加队列排序的功能,将高优先级排在前面,所以优先级队列的元素需要实现Comparator接口
如果数据结构用数组去维护队列的话,要么在put有大量的后移操作,要么在take有大量的前移操作。
为避免这个问题优先级队列内部用二叉堆的数据结构去实现,这样无论是put还是take都不会有大量的移动操作。具体逻辑如下:
put:如果优先级比父节点高就上浮,依次类推,直至不能再上浮
take:直接拿走堆顶元素,然后再用最后一个元素顶上堆顶,再根据优先级下沉该元素

不懂二叉堆的,可以先去查阅一下二叉堆或者堆排序。
虽然二叉堆不是完全有序的,但可以保证堆顶元素的优先级肯定是最高的。

put

    public void put(E e) {
        offer(e); //优先级队列是无界的,所以不需要阻塞,直接调用offer
    }
	
	//入队
    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        int n, cap;
        Object[] es;
        //扩容
        //先释放锁,开辟新数组(长度= size<64? size+2 : size*1.5。)
        //之后再重新加锁复制到新数组
        while ((n = size) >= (cap = (es = queue).length))
            tryGrow(es, cap);
        try {
            final Comparator<? super E> cmp;
            //如果没有传入比较器就用默认的
            if ((cmp = comparator) == null)
            	//二叉堆节点上浮
                siftUpComparable(n, e, es);
            else
                siftUpUsingComparator(n, e, es, cmp);
            size = n + 1;
            //有元素了,不为空,notEmpty.signal
            notEmpty.signal();
        } finally {
        	//释放锁
            lock.unlock();
        }
        return true;
    }
	
	//上浮
    private static <T> void siftUpComparable(int k, T x, Object[] es) {
        Comparable<? super T> key = (Comparable<? super T>) x;
        //如果k=0,说明上浮到堆顶了
        while (k > 0) {
        	//二叉堆父节点在数据的下标=(当前节点下标-1)/2
            int parent = (k - 1) >>> 1;
            Object e = es[parent];
            //不能再上浮了就break
            if (key.compareTo((T) e) >= 0)
                break;
            //可以上浮,交换当前节点和父节点    
            es[k] = e;
            k = parent;
        }
        es[k] = key;
    }

take

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lockInterruptibly();
        E result;
        try {
        	//dequeue出队一个元素
            while ( (result = dequeue()) == null)
                //队列为空,notEmpty.await 阻塞
                notEmpty.await();
        } finally {
        	//释放锁
            lock.unlock();
        }
        return result;
    }

	//出队
	private E dequeue() {
        final Object[] es;
        final E result;
		
		//堆顶记入result
        if ((result = (E) ((es = queue)[0])) != null) {
            final int n;
            //--size并将最后一个元素记入x后清空(赋值null)
            final E x = (E) es[(n = --size)];
            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;
    }
	
	//下沉
    private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {
        Comparable<? super T> key = (Comparable<? super T>)x;
        //假如这里是 (n-1)/2 就是(前面被清空的那个)最后一个元素x的父节点下标
        //但这里是 (n/2),意味着当最后一个元素x是左孩子节点的话half就是父节点下标,右孩子节点的话half就是父节点下标+1
        int half = n >>> 1;
        //k>=half说明下沉到底了
        while (k < half) {
        	//n*2+1就是左孩子节点
            int child = (k << 1) + 1;
            Object c = es[child];
            //右孩子节点=左孩子节点+1
            int right = child + 1;
            //在两个孩子节点中取优先级比较高去跟下沉节点比较
            if (right < n &&
                ((Comparable<? super T>) c).compareTo((T) es[right]) > 0)
                c = es[child = right];
            //如果不能再下沉了就break
            if (key.compareTo((T) c) <= 0)
                break;
            //可以下沉,交换当前节点和被选中的孩子节点  
            es[k] = c;
            k = child;
        }
        //最后别忘了替换下沉堆顶的值为被清空的最后一个元素x
        es[k] = key;
    }

2、DelayQueue

延迟队列是在优先级队列的基础上实现的(优先级按延迟时间排序),其内部维护了一个优先级队列。

注:是优先级队列而不是优先级阻塞队列,这两者的区别在于有无阻塞。
为什么要用优先级队列不用优先级阻塞队列?
因为不适用。延迟队列的put不需要阻塞,而take则是需要自己实现一个根据延迟时间来阻塞的逻辑。

在这里插入图片描述
put

    public void put(E e) {
        offer(e); //因为不需要阻塞,所以直接调用offer即可
    }

    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
        	//优先级队列 PriorityQueue.offer() 入队
            q.offer(e);
            //如果入队后是第一个元素,需要更新leader的阻塞时间
            if (q.peek() == e) {
            	//清空leader,leader记录带超时时间等待阻塞队列头节点的线程(只有一个)
                leader = null;
                //唤醒所有正在等待的线程重新take(自旋),以更新leader的阻塞时间,同时leader也可能会变
                available.signal();
            }
            return true;
        } finally {
        	//释放锁
            lock.unlock();
        }
    }

take

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lockInterruptibly();
        try {
            for (;;) {
            	//第一个节点
                E first = q.peek();
                //如果为空阻塞,available.await()
                if (first == null)
                    available.await();
                else {
                	//如果延迟时间到了,出队
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0L)
                        return q.poll();
                    first = null;
                    //下面的就是延迟时间还没到
                    
                    //如果已经有leader了,就不带超时时间的阻塞,后续由leader唤醒
                    if (leader != null)
                        available.await();
                    //如果还没有leader,就记录leader是当前线程带并超时时间的阻塞
                    else {
                    	//记录leader是当前线程
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                        	//带超时时间的阻塞
                            available.awaitNanos(delay);
                        } finally {
                        	//阻塞时间到了,清空leader
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
        	//如果leader为空(leader的阻塞时间已到,此时leader已经获取到资源了)
        	//且队列中还有资源
        	//就唤醒后面等待的线程
            if (leader == null && q.peek() != null)
                available.signal();
            //释放锁
            lock.unlock();
        }
    }

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

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

相关文章

uniapp系列-图文并茂教你配置uniapp开发环境

环境安装 1. 安装 node.js (版本 18/16) 在搭建 Vue 开发环境之前&#xff0c;请先下载 node.js。 Node 可从官方网站下载&#xff0c;也可从中文网站下载。根据你的电脑选择 32 位 或 64 位。网站&#xff1a; Node 或者访问 历史版本 查看 node 版本 C:\Users> node -…

超级干货 | 数据平滑9大妙招(python版)

大家好&#xff0c;对数据进行平滑处理的方法有很多种&#xff0c;具体的选择取决于数据的性质和处理的目的。今天给大家分享9大常见数据平滑方法&#xff1a; 移动平均Moving Average 指数平滑Exponential Smoothing 低通滤波器 多项式拟合 贝塞尔曲线拟合 局部加权散点平…

【python海洋专题二十一】subplots共用一个colorbar

上期读取subplot&#xff0c;并出图 但是存在一些不完美&#xff0c;本期修饰 本期内容 共用colorbar 1&#xff1a;未共用colorbar 共用colorbar 1&#xff1a;横 2&#xff1a;纵 关键语句 图片 cb_ax fig.add_axes([0.15, 0.02, 0.6, 0.03]) #设置colarbar位置 cbar …

切换npm的版本

1、在配置环境变量的地址中&#xff0c;多准备几个已解压版本的node 2、要想升降版本直接更改该文件中的文件夹名称就行 环境变量中的path的值是不用变的C:\Program Files\nodejs

Leetcode 剑指 Offer II 048. 二叉树的序列化与反序列化

题目难度: 困难 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 序列化是将一个数据结构或者对象转换为连续的比特位的操作&#…

商品分类代码

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>商品分类代码</title> <script type"text/javascript"> function MM_preloadImages() { //v3.0var ddocument; if(d.images){ if(!d.MM_p) d.MM_p…

5+甲基化+预后模型搭配实验

今天给同学们分享一篇甲基化预后模型实验的生信文章“Six immune-related promising biomarkers may promote hepatocellular carcinoma prognosis: a bioinformatics analysis and experimental validation”&#xff0c;这篇文章于2023年3月23日发表在Cancer Cell Int期刊上&…

三极管和MOS如何导通

三极管类型 原理图分析三极管&#xff0c;先看看它是什么类型&#xff0c;是PNP还是NPN。 一般通过看E极&#xff08;发射极&#xff09;流向&#xff0c;从B&#xff08;基极&#xff09;到E&#xff08;发射极&#xff09;为NPN。从E&#xff08;发射极&#xff09;到B&…

新的U-Net 网络结构

最近看到一篇很有趣的文章&#xff0c;Rethinking the unpretentious U-net for medical ultrasound image segmentation 这个文章提出了一种新的U-Net 网络结构。以前大家使用U-Net 喜欢加入新的模块或者使用多个U-Net 并联的方法进行语义分割。这篇文章提出了一种的新U-Net 结…

人工智能聊天机器人如何满足企业和客户不断变化的需求?

随着数字化转型的加速&#xff0c;企业与客户之间的沟通方式也在发生变化。传统的电话和电子邮件已经无法满足客户的即时需求和个性化体验。而人工智能聊天机器人作为一种智能助手&#xff0c;通过其快速、便捷和智能的特点&#xff0c;正在成为企业与客户之间沟通的新方式。 |…

第四章 网络层 | 计算机网络(谢希仁 第八版)

文章目录 第四章 网络层4.1 网络层提供的两种服务4.2 网际协议IP4.2.1 虚拟互连网络4.2.2 分类的IP地址4.2.3 IP地址与硬件地址4.2.4 地址解析协议ARP4.2.5 IP数据报的格式4.2.6 IP层转发分组的流程 4.3 划分子网和构造超网4.3.1 划分子网4.3.2 使用子网时分组的转发4.3.3 无分…

jwt->jwt简介,jwt工具类,jwt集进成spa项目

1.jwt简介 JWT是什么&#xff1f; JSON Web Token (JWT)&#xff0c;它是目前最流行的跨域身份验证解决方案 为什么使用JWT&#xff1f; JWT的精髓在于&#xff1a;“去中心化”&#xff0c;数据是保存在客户端的。 JWT的工作原理 1. 是在服务器身份验证之后&#xff…

【C++】C++11 ——— 可变参数模板

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C学习 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C】STL…

《PyTorch深度学习实践》第二讲 线性模型

《PyTorch深度学习实践》第二讲 线性模型 问题描述问题分析代码实现效果课后练习 资源均来自 B站 刘二大人 &#xff0c;传送门线性模型 问题描述 问题分析 代码 import numpy as np import matplotlib.pyplot as pltx_data [1.0, 2.0, 3.0] y_data [2.0, 4.0, 6.0]# 定义模…

python+深度学习+opencv实现植物识别算法系统 计算机竞赛

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习的植物识别算法研究与实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;4分工作量&#xff1a;4分创新点&#xff1a;4分 &#x1f9ff; 更多…

js深拷贝与浅拷贝

1.浅拷贝概念 浅拷贝是其属性与拷贝源对象的属性共享相同引用&#xff0c;当你更改源或副本时&#xff0c;也可能&#xff08;可能说的是只针对引用数据类型&#xff09;导致其他对象也发生更改。 特性&#xff1a; 会新创建一个对象&#xff0c;即objobj2返回fasle&#xf…

【C++项目】高并发内存池第一讲(项目整体框架介绍、哈系统结构设计)

高并发内存池项目第一讲 一、高并内存池概念二、项目介绍三、项目细节四.哈系统结构设计 一、高并内存池概念 内存池(Memory Pool) 是一种动态内存分配与管理技术。 通常情况下&#xff0c;程序员习惯直接使用 new、delete、malloc、free 等API申请分配和释放内存&#xff0c;…

闭包及底层原理

1.闭包概念 定义&#xff1a;能够访问到其他函数作用域中的对象的函数&#xff0c;称为闭包 误区&#xff1a;闭包不是函数里面嵌套函数 2.闭包的两种写法 2.1函数嵌套写法 // 闭包写法1: 内部嵌套函数function fn(){var a 1;function fn2(){console.log(a1);}fn2();}fn()…

在线兴趣教学类线上学习APP应用开发部署程序组建研发团队需要准备什么?

哈哈哈&#xff0c;同学们&#xff0c;我又来了&#xff0c;这个问题最近问的人有点多&#xff0c;但是说实话我也不知道&#xff0c;但是我还是总结了一下&#xff0c;毕竟我懂点代码的皮毛&#xff0c;同时我检索内容的时候&#xff0c;都是一些没有很新鲜的文案&#xff0c;…

Vue中调用组件使用kebab-case(短横线)命名法和使用大驼峰的区别

文章目录 Vue中调用组件使用kebab-case&#xff08;短横线&#xff09;命名法和使用大驼峰的区别1.解析官网手册说明2.什么是“字符串模版”&#xff0c;什么是“dom模版” Vue中调用组件使用kebab-case&#xff08;短横线&#xff09;命名法和使用大驼峰的区别 1.解析官网手册…