Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >(juc系列)延迟队列delayqueue

(juc系列)延迟队列delayqueue

作者头像
呼延十
发布于 2021-11-10 03:20:45
发布于 2021-11-10 03:20:45
51300
代码可运行
举报
文章被收录于专栏:呼延呼延
运行总次数:0
代码可运行

本文源码基于: JDK13

DelayQueue 延迟队列

官方注释翻译

用于延迟元素的一个无界的阻塞队列实现. 延迟元素只有在他的延迟过期之后,才可以被获取.

队头的元素,是队列中过期最早的元素。如果没有元素过期,那么将没有队头元素,poll方法将会返回一个null.

过期操作只有元素的getDelay方法返回一个小于等于0的数值时才会起作用.

尽管没有过期的元素,不能通过take或者poll来获取, 其他方面和正常的元素是一样的.

比如,size()返回过期和未过期的元素的计数,同时,这个队列也是不接受空元素.

这个类和他的迭代器实现了CollectionIterator接口的所有可选方法.

这个类也是Java集合框架的一部分噢。

源码

定义
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {

首先是一个普通队列, 且还是阻塞队列. 拥有他们的所有属性,同时,还要求放入的元素,是实现了Delayed接口的. 该接口定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface Delayed extends Comparable<Delayed> {

    /**
     * Returns the remaining delay associated with this object, in the
     * given time unit.
     *
     * @param unit the time unit
     * @return the remaining delay; zero or negative values indicate
     * that the delay has already elapsed
     */
    long getDelay(TimeUnit unit);
}

根据给定的时间单位,返回剩余的延迟时间.

属性
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // 锁
    private final transient ReentrantLock lock = new ReentrantLock();
    // 优先级队列
    private final PriorityQueue<E> q = new PriorityQueue<E>();

    // 正在等待队头元素的线程
    private Thread leader;

    // 有元素可用的等待条件
    private final Condition available = lock.newCondition();

使用优先级队列来保存元素,同时记录等待队首元素的线程.

这个优先级队列,是java.util包里的,暂不做详细解释,相信大家都懂哈.

提供了等待条件available来负责阻塞线程与唤醒.

构造方法
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public DelayQueue() {}

    public DelayQueue(Collection<? extends E> c) {
        this.addAll(c);
    }

提供两个构造方法,分别构造一个空的延迟队列和一个加载给定集合的阻塞队列.

入队系列
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public boolean add(E e) {
        return offer(e);
    }

    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 插入元素
            q.offer(e);
            // 队头元素是刚才插入的元素,说明有可用元素,唤醒等待线程们
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

    public void put(E e) {
        offer(e);
    }

    public boolean offer(E e, long timeout, TimeUnit unit) {
        return offer(e);
    }

4个入队系列的方法,本质上都是调用了offer. 直接调用内部优先级队列的offer,无脑写入即可.

可以看到,该方法永远返回ture. 因为这个延迟队列也是无界的,因此不需要阻塞,不会插入失败.

插入只有两种可能:

  1. 成功
  2. 内存爆了,程序死掉.
出队系列
poll 没有元素返回Null
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 获取第一个元素
            E first = q.peek();
            // 第一个元素为空,或者第一个元素的延迟时间没有到期,返回null.
            // 否则返回该元素
            return (first == null || first.getDelay(NANOSECONDS) > 0)
                ? null
                : q.poll();
        } finally {
            lock.unlock();
        }
    }

首先查看第一个元素,如果不为空且已经过期了,那就弹出进行返回. 否则就返回null.

take 阻塞等待
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 自旋
            for (;;) {
                //  查看第一个元素
                E first = q.peek();
                // 第一个元素为空,直接等待
                if (first == null)
                    available.await();
                else {
                    // 第一个元素已经超时,可用了,就进行弹出
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0L)
                        return q.poll();
                    // 等待
                    first = null; // don't retain ref while waiting
                    if (leader != null)
                        available.await();
                    else {
                        // 如果当前是第一个等待队首元素的线程,记录一下当前线程,且只阻塞剩余的时间,就苏醒来检查一下是否可用了
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            // 拿到元素后,协助唤醒一下等待线程
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

这个阻塞版本的获取元素复杂一点.

  1. 如果第一个元素为空, 就让当前线程阻塞等待.
  2. 不为空,且已经过期,直接弹出,进行返回,此时获取元素成功.
  3. 不为空,且没有过期,如果当前线程,是第一个等待队首元素的线程, 就阻塞第一个元素剩余的延迟时间, 到期后苏醒来检查队首元素的状态.
  4. 不是第一个等待的线程,直接阻塞,等待第一个线程来唤醒.
  5. 获取元素成功后,如果还有可用元素,协助唤醒一下其余的等待线程.
poll(time,unit) 超时阻塞版本

和上面的take代码很像,只是在每一个线程的阻塞时都加上了时间限制,就不重复讲了.

查看系列
size 查看元素数量

这个简单的方法为啥要写呢,因为要注意: 返回的size,是所有过期的,未过期的总数.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int size() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.size();
        } finally {
            lock.unlock();
        }
    }

直接调用了内部的优先级队列的size()方法,没有判断是否过期.

peek() 查看队首元素,不弹出

由于在延迟队列中,总是需要看一下,队首元素,如果已经过期,就弹出,没过期,就不处理. 因此也简单看一下peek()方法.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.peek();
        } finally {
            lock.unlock();
        }
    }

没啥,加锁,然后调用优先级队列的peek完事了。

总结

延迟队列,本质上是一个带有优先级的阻塞队列,且根据延迟限制队首元素的出队.

  • 优先级队列的实验,使用了java.util.PriorityQueue,本质上实现应该也是一个堆实现的.
  • 阻塞队列的实现,使用Condition条件. 由于是无界队列,入队操作不会阻塞. 出队行为在条件上等待,当有符合条件的元素时,唤醒所有等待线程.
  • 延迟属性的实现,在出队时,对队首元素进行额外的过期判断,如果过期,就弹出,没有过期,就返回null.
  • 线程安全方面,由于java.util.PriorityQueue不是线程安全的,因此使用额外的一个ReentrantLock来限制对数据的读写访问.

参考文章

完。

联系我

最后,欢迎关注我的个人公众号【 呼延十 】,会不定期更新很多后端工程师的学习笔记。 也欢迎直接公众号私信或者邮箱联系我,一定知无不言,言无不尽。

以上皆为个人所思所得,如有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文链接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见个人博客或关注微信公众号 <呼延十 >——>呼延十

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
并发队列-无界阻塞延迟队列DelayQueue原理探究
DelayQueue队列中每个元素都有个过期时间,并且队列是个优先级队列,当从队列获取元素时候,只有过期元素才会出队列。
加多
2018/09/06
9340
并发队列-无界阻塞延迟队列DelayQueue原理探究
阻塞队列实现之DelayQueue源码解析
DelayQueue是一个支持延时获取元素的无界阻塞队列,使用PriorityQueue来存储元素。
烂猪皮
2023/09/04
1900
阻塞队列实现之DelayQueue源码解析
DelayQueue详解
  【1】DelayQueue 是一个支持延时获取元素的阻塞队列, 内部采用优先队列 PriorityQueue 存储元素,同时元素必须实现 Delayed 接口;在创建元素时可以指定多久才可以从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。延迟队列的特点是:不是先进先出,而是会按照延迟时间的长短来排序,下一个即将执行的任务会排到队列的最前面。注意:不能将null元素放置到这种队列中。
忧愁的chafry
2022/10/30
5910
Java Review - 并发编程_DelayQueue原理&源码剖析
DelayQueue并发队列是一个无界阻塞延迟队列,队列中的每个元素都有个过期时间,当从队列获取元素时,只有过期元素才会出队列。
小小工匠
2021/12/30
4100
Java Review - 并发编程_DelayQueue原理&源码剖析
DelayQueue 源码分析
我们先来看一下它的实现类图,它实现了 Delayed、BlockingQueue 接口和 AbstractQueue 基础类,从实现的功能上看,它首先是一个阻塞队列,然后 Delayed 接口是标记给定延迟后执行的对象,结合类名也可以大致的分析出:DelayQueue 是一个 延时阻塞 队列
itliusir
2020/01/06
6150
【死磕Java并发】-----J.U.C之阻塞队列:DelayQueue
DelayQueue是一个支持延时获取元素的无界阻塞队列。里面的元素全部都是“可延期”的元素,列头的元素是最先“到期”的元素,如果队列里面没有元素到期,是不能从列头获取元素的,哪怕有元素也不行。也就是说只有在延迟期到时才能够从队列中取元素。 DelayQueue主要用于两个方面: 缓存:清掉缓存中超时的缓存数据 任务超时处理 DelayQueue DelayQueue实现的关键主要有如下几个: 可重入锁ReentrantLock 用于阻塞和通知的Condition对象 根据Delay时间排序的优先级队列:P
用户1655470
2018/04/26
7960
每日一博 - DelayQueue阻塞队列源码解读
DelayQueue 由优先级支持的、基于时间的调度队列,内部使用非线程安全的优先队列(PriorityQueue)实现,而无界队列基于数组的扩容实现。
小小工匠
2021/10/08
4290
死磕 java集合之DelayQueue源码分析
从继承体系可以看到,DelayQueue实现了BlockingQueue,所以它是一个阻塞队列。
彤哥
2019/07/08
4600
死磕 java集合之DelayQueue源码分析
在阿里面试官面前现场手撕DelayQueue源码!
延迟元素的无边界阻塞队列,在该队列中,仅当元素的延迟到期时才可以使用它. 队首是该 Delayed 元素,其延迟在过去最远过期. 如果没有延迟已经过期,就没有head, poll将返回null. 当元素的getDelay(TimeUnit.NANOSECONDS)方法返回的值小于或等于零时,就会发生过期. 即使未到期的元素无法使用take或poll删除,它们也被视为普通的元素。 例如,size方法返回过期和未过期元素的计数. 此队列不允许空元素. 该类及其迭代器实现集合和迭代器接口的所有可选方法。方法Iterator()中提供的迭代器不能保证以任何特定的顺序遍历DelayQueue中的元素.
JavaEdge
2020/05/27
6920
在阿里面试官面前现场手撕DelayQueue源码!
Java 延时队列 DelayQueue
java延迟队列提供了在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素。没有过期元素的话,使用poll()方法会返回null值,超时判定是通过getDelay(TimeUnit.NANOSECONDS)方法的返回值小于等于0来判断。延时队列不能存放空元素。
一个会写诗的程序员
2020/05/29
2.2K0
Java 延时队列 DelayQueue
DelayQueue 核心源码解析
当元素的getDelay(TimeUnit.NANOSECONDS)方法返回的值小于或等于零时,就会发生过期.
JavaEdge
2020/05/03
3260
DelayQueue 核心源码解析
JDK源码分析-DelayQueue
DelayQueue 也是一种队列,它内部的元素有“延迟”,也就是当从队列中获取元素时,如果它的延迟时间未到,则无法取出。
WriteOnRead
2019/10/16
3510
Java并发队列原理剖析
LinkedBlockingQueue和ArrayBlockingQueue比较简单,不进行讲解了。下面只介绍PriorityBlockingQueue和DelayQueue。
用户4283147
2022/10/27
2620
Java并发队列原理剖析
JDK容器学习之Queue:DelayQueue
延迟阻塞队列 DelayQueue 阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞 延迟阻塞队列DelayQueue的底层是基于优先级队列PriorityQueue来实现的,因此研究延迟阻塞队列,更多的注意力应集中在以下两点 阻塞是如何实现的 应用场景是什么 I. 阻塞队列的实现逻辑 1. 限定 类的声明如下,要求队列中的元素必须继承 Delayed public class DelayQueue<E extends Del
一灰灰blog
2018/02/06
7410
【原创】Java并发编程系列32 | 阻塞队列(下)
阻塞队列在并发编程非常常用,被广泛使用在“生产者-消费者”问题中。本文是阻塞队列下篇。
java进阶架构师
2020/08/28
4420
JUC-BlockingQueue二
LinkedTransferQueue是一个由链表结构组成的无界阻塞队列,相对于其它阻塞队列,LinkedBlockingQueue可以算是LinkedBlockingQueue与SynhronoousQueue结合,LinkedtransferQueue是一种无界阻塞队列,底层基于单链表实现,其内部结构分为数据节点、请求节点,基于CAS无锁算法实现
才疏学浅的木子
2023/10/17
1430
JUC-BlockingQueue二
多线程之阻塞队列
DelayQueue每次都是将元素加入排序队列,以delay/过期时间为排序因素,将快过期的元素放在队首,取数据的时候每次都是先取快过期的元素。 构造方法
OPice
2019/10/23
8960
java杂谈之并发容器
前几天和同事xhf、zm走查代码,功能是为了减少频繁你创建FTP开销用线程notify和wait实现了一个FTP池子,当时提的建议就是用java自带的线程集合实现可能更高效,本文整理下JDK自带线程安全的集合,不考虑多线程并发的情况下,容器类一般使用 ArrayList、HashMap 等线程不安全的类,效率更高。在并发场景下,常会用到ConcurrentHashMap、ArrayBlockingQueue 等线程安全的容器类,虽然牺牲了一些效率,但却得到了安全。
你呀不牛
2021/05/28
4590
自己动手系列-延迟队列
1.什么是延迟队列 在java的并发包中有有关定时调度的api。 里边其中一个重要实现就是延迟队列,通过延时队列来实现定时调度。 那么如果让你实现一个延时队列,你会怎么做呢? 2.自己实现一个延迟队列 2.1.定义一个Delayed接口。 2.2.定义一个DelayQueue。 2.2.1.继承AbstractQueue 2.2.2.实现BlockingQueue 2.2.2.使用PriorityQueue来装载任务 2.2.3.使用重入锁Re
ImportSource
2018/04/03
2.9K0
自己动手系列-延迟队列
Juc并发编程11——深入源码:常用并发容器、阻塞队列使用与原理
本文将介绍常用的并发容器,比较传统容器与并发容器的区别,介绍并发容器的基本原理。是面试常考、工作常用的热门知识点。
半旧518
2022/10/26
3410
Juc并发编程11——深入源码:常用并发容器、阻塞队列使用与原理
相关推荐
并发队列-无界阻塞延迟队列DelayQueue原理探究
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验