DelayQueue延迟队列原理剖析

介绍

DelayQueue队列是一个延迟队列,DelayQueue中存放的元素必须实现Delayed接口的元素,实现接口后相当于是每个元素都有个过期时间,当队列进行take获取元素时,先要判断元素有没有过期,只有过期的元素才能出队操作,没有过期的队列需要等待剩余过期时间才能进行出队操作。

源码分析

DelayQueue队列内部使用了PriorityQueue优先队列来进行存放数据,它采用的是二叉堆进行的优先队列,使用ReentrantLock锁来控制线程同步,由于内部元素是采用的PriorityQueue来进行存放数据,所以Delayed接口实现了Comparable接口,用于比较来控制优先级,如下代码所示:

 1public interface Delayed extends Comparable<Delayed> {
2
3    /**
4     * Returns the remaining delay associated with this object, in the
5     * given time unit.
6     *
7     * @param unit the time unit
8     * @return the remaining delay; zero or negative values indicate
9     * that the delay has already elapsed
10     */
11    long getDelay(TimeUnit unit);
12}

DelayQueue的成员变量如下所示:

 1// 锁。
2private final transient ReentrantLock lock = new ReentrantLock();
3// 优先队列。
4private final PriorityQueue<E> q = new PriorityQueue<E>();
5
6/**
7 * Leader-Follower的变种。
8 * Thread designated to wait for the element at the head of
9 * the queue.  This variant of the Leader-Follower pattern
10 * (http://www.cs.wustl.edu/~schmidt/POSA/POSA2/) serves to
11 * minimize unnecessary timed waiting.  When a thread becomes
12 * the leader, it waits only for the next delay to elapse, but
13 * other threads await indefinitely.  The leader thread must
14 * signal some other thread before returning from take() or
15 * poll(...), unless some other thread becomes leader in the
16 * interim.  Whenever the head of the queue is replaced with
17 * an element with an earlier expiration time, the leader
18 * field is invalidated by being reset to null, and some
19 * waiting thread, but not necessarily the current leader, is
20 * signalled.  So waiting threads must be prepared to acquire
21 * and lose leadership while waiting.
22 */
23private Thread leader = null;
24
25/**
26 * Condition signalled when a newer element becomes available
27 * at the head of the queue or a new thread may need to
28 * become leader.
29 */
30// 条件,代表如果有数据则通知Follower线程,唤醒线程处理队列内容。
31private final Condition available = lock.newCondition();

Leader-Follower模式的变种,用于最小化不必要的定时等待,当一个线程被选择为Leader时,它会等待延迟过去执行代码逻辑,而其他线程则需要无限期等待,在从take或poll返回之前,每当队列的头部被替换为具有更早到期时间的元素时,leader字段将通过重置为空而无效,Leader线程必须向其中一个Follower线程发出信号,被唤醒的 follwer 线程被设置为新的Leader 线程。

offer操作

 1public boolean offer(E e) {
2    // 获取到锁
3    final ReentrantLock lock = this.lock;
4    lock.lock();
5    try {
6        // 将元素存储到PriorityQueue优先队列中
7        q.offer(e);
8        // 如果第一个元素是当前元素,说明之前队列中为空,则先将Leader设置为空,通知等待线程可以争抢Leader了。
9        if (q.peek() == e) {
10            leader = null;
11            available.signal();
12        }
13        // 返回成功
14        return true;
15    } finally {
16        lock.unlock();
17    }
18}

offer操作前先进行获取锁的操作,也就是同一时间内只能有一个线程可以入队操作。

  1. 获取到ReentrantLock锁对象。
  2. 将元素添加到PriorityQueue优先队列中
  3. 如果队列中最早过期的元素是自己,则说明队列原先是空的,所以将Leader进行重置,通知Follower线程可以成为Leader线程。
  4. 最后进行解锁操作。

put操作

put操作其实就是调用的offer操作来进行添加数据的,以下是源码信息:

1public void put(E e) {
2    offer(e);
3}

take操作

 1public E take() throws InterruptedException {
2    final ReentrantLock lock = this.lock;
3    // 获取可中断的锁。
4    lock.lockInterruptibly();
5    try {
6        // 循环获取数据。
7        for (;;) {
8            // 获取最早过期的元素,但是不弹出对象。
9            E first = q.peek();
10            // 如果最早过期的元素为空,说明队列为空,则线程直接进入无限期等待,并且让出锁。
11            if (first == null)
12                // 当前线程无限期等待,直到被唤醒,并且让出锁对象。
13                available.await();
14            else {
15                // 获取最早过期的元素剩余过期时间。
16                long delay = first.getDelay(NANOSECONDS);
17                // 如果剩余过期时间小于0,则说明已经过期,反之还没有过期。
18                if (delay <= 0)
19                    // 如果已经过期直接获取最早过期的元素,并返回。
20                    return q.poll();
21                // 如果剩余过期日期大于0,则会进入到这里。
22                // 将刚才获取的最早过期的元素设置为空。
23                first = null; // don't retain ref while waiting
24                // 如果有线程争抢的Leader线程,则进行无限期等待。
25                if (leader != null)
26                    // 无限期等待并让出锁。
27                    available.await();
28                else {
29                    // 获取当前线程。
30                    Thread thisThread = Thread.currentThread();
31                    // 设置当前线程变为Leader线程。
32                    leader = thisThread;
33                    try {
34                        // 等待剩余等待时间。
35                        available.awaitNanos(delay);
36                    } finally {
37                        // 将Leader设置为null。
38                        if (leader == thisThread)
39                            leader = null;
40                    }
41                }
42            }
43        }
44    } finally {
45        // 如果队列不为空,并且没有Leader则通知等待线程可以成为Leader。
46        if (leader == null && q.peek() != null)
47            // 通知等待线程。
48            available.signal();
49        lock.unlock();
50    }
51}
  1. 当获取元素时,先获取到锁对象。
  2. 获取最早过期的元素,但是并不从队列中弹出元素。
  3. 最早过期元素是否为空,如果为空则直接让当前线程无限期等待状态,并且让出当前锁对象。
  4. 如果最早过期的元素不为空
  • 获取最早过期元素的剩余过期时间,如果已经过期则直接返回当前元素
  • 如果没有过期,也就是说剩余时间还存在,则先获取Leader对象,如果Leader已经有线程在处理,则当前线程进行无限期等待,如果Leader为空,则首先将Leader设置为当前线程,并且让当前线程等待剩余时间。
  • 最后将Leader线程设置为空
  1. 如果Leader已经为空,并且队列有内容则唤醒一个等待的队列。

poll操作

获取最早过期的元素,如果队列头没有过期的元素则直接返回null,反之返回过期的元素。

 1public E poll() {
2    final ReentrantLock lock = this.lock;
3    lock.lock();
4    try {
5        E first = q.peek();
6        // 如果队列为空或者队列最早过期的元素没有过期,则返回null。
7        if (first == null || first.getDelay(NANOSECONDS) > 0)
8            return null;
9        else
10            // 出队列操作。
11            return q.poll();
12    } finally {
13        lock.unlock();
14    }
15}

小结

  1. DelayQueue是一个无界的并发延迟阻塞队列,队列中的元素必须实现Delayed接口,相应了需要实现Comparable接口实现比较的方法
  2. Leader-Follower模式的变种,用于最小化不必要的定时等待,当一个线程被选择为Leader时,它会等待延迟过去执行代码逻辑,而其他线程则需要无限期等待,在从take或poll返回之前,每当队列的头部被替换为具有更早到期时间的元素时,leader字段将通过重置为空而无效,Leader线程必须向其中一个Follower线程发出信号,被唤醒的 follwer 线程被设置为新的Leader 线程。

喜欢的同学点赞关注下微信公众号,推送优质文章。

DelayQueue延迟队列原理剖析的更多相关文章

  1. DelayQueue延迟队列-实现缓存

    延迟阻塞队列DelayQueue DelayQueue 是一个支持延时获取元素的阻塞队列, 内部采用优先队列 PriorityQueue 存储元素, 同时元素必须实现 Delayed 接口:在创建元素 ...

  2. 延迟队列DelayQueue take() 源码分析

    延迟队列DelayQueue take() 源码分析 在工作中使用了延迟队列,对其内部的实现很好奇,于是就研究了一下其运行原理,在这里就介绍一下take()方法的源码 1 take()源码 如下所示 ...

  3. 延迟队列DelayQueue

    应用场景:有一批广告需要不定时上下架,有可能上下架的时间间隔很长,就没必要用定时器轮询,用延迟队列进行任务执行. public class Test2 { public static void mai ...

  4. 《java学习三》并发编程 -------线程池原理剖析

    阻塞队列与非阻塞队 阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞.试图从空的阻塞队列中获取元素的线程将会被阻塞,直到 ...

  5. JUC——延迟队列

    所谓的延迟队列最大的特征是它可以自动通过队列进行脱离,例如:现在有一些对象被临时保存着,但是有可能该集合对象是一个公共对象,那么里面的某些数据如果不在使用的时候就希望其可以在指定的时间达到后自动的消失 ...

  6. PHP基于Redis实现轻量级延迟队列

    延迟队列,顾名思义它是一种带有延迟功能的消息队列. 那么,是在什么场景下我才需要这样的队列呢? 一.背景 先看看一下业务场景: 1.会员过期前3天发送召回通知 2.订单支付成功后,5分钟后检测下游环节 ...

  7. 灵感来袭,基于Redis的分布式延迟队列

    延迟队列 延迟队列,也就是一定时间之后将消息体放入队列,然后消费者才能正常消费.比如1分钟之后发送短信,发送邮件,检测数据状态等. Redisson Delayed Queue 如果你项目中使用了re ...

  8. RocketMQ应用及原理剖析

    主流消息队列选型对比分析 基础项对比 可用性.可靠性对比 功能性对比 对比分析 Kafka:系统间的流数据通道 RocketMQ:高性能的可靠消息传输 RabbitMQ:可靠消息传输 RocketMQ ...

  9. RabbitMQ使用 prefetch_count优化队列的消费,使用死信队列和延迟队列实现消息的定时重试,golang版本

    RabbitMQ 的优化 channel prefetch Count 死信队列 什么是死信队列 使用场景 代码实现 延迟队列 什么是延迟队列 使用场景 实现延迟队列的方式 Queue TTL Mes ...

随机推荐

  1. MyBatisPlus入门学习

    目录 MyBatisPlus 概述 快速入门 配置日志输出 CRUD拓展 插入 主键生成策略 更新操作 自动填充 乐观锁 查询操作 分页查询 删除操作 逻辑删除 性能分析插件 条件构造器 代码自动生成 ...

  2. SpringCloud-Stream消息通信

    SpringCloud微服务实战系列教程 Spring Cloud Stream 消息驱动组件帮助我们更快速,更⽅便,更友好的去构建消息驱动微服务的.当时定时任务和消息驱动的⼀个对⽐.(消息驱动:基于 ...

  3. WPScan工具的使用(WordPress扫描工具)

    WPScan是Kali Linux默认自带的一款漏洞扫描工具,它采用Ruby编写,能够扫描WordPress网站中的多种安全漏洞,其中包括WordPress本身的漏洞.插件漏洞和主题漏洞.最新版本WP ...

  4. PowerShell-2.解决禁止本地执行脚本

    现象 直接找到XXX.ps1右键 使用PowerShell运行没反应,然后打开PowerShel把脚本推进去显示这个: 原因是因为脚本执行权限默认是Restricted,这个是禁止执行任何本地脚本. ...

  5. v-bind的使用

    v-bind v-bind的引入 ​ 内容的绑定可以通过mustache语法,而属性的绑定往往需要通过v-bind 如动态绑定img的src属性 如动态绑定div的class属性 如动态绑定li元素的 ...

  6. Codeforces Round #687 (Div. 2, based on Technocup 2021 Elimination Round 2)

    A. Prison Break 题意:就是在一个n*m的矩阵中,以(1,1)为起点(n,m)为终点,每个点以每个单位1s的速度移动,问总共至少需要多少秒,所有的矩阵点就能够全部移动到(r,c)中 思路 ...

  7. 【近取 Key】Alpha - v1.0 版本发布说明

    功能与特性 Alpha 版本虽然为本软件的第一代版本,但已基本覆盖了用户个人使用时的主要功能.除登陆注册与后台管理外,下文将分版块详细介绍面向用户的主要功能特性. 『产品主页』 潜在应用场景 场景 0 ...

  8. 团队任务拆解$\alpha$

    项目 内容 班级:2020春季计算机学院软件工程(罗杰 任健) 博客园班级博客 作业要求 团队任务拆解 我们在这个课程中的目标 提升团队管理及合作能力,开发一项满意的工程项目 这个作业对我们实现目标的 ...

  9. CSS3过渡应用

    小米图标转换 transition:需要过渡的属性 花费时间 (运动曲线 何时开始): Tips: 1.第二个属性值必须跟上单位(s) 2.谁要过渡给谁加 图标转换最终效果:当鼠标划过图标时,缓慢转换 ...

  10. 聊一聊 JVM 的 GC

    原文链接:https://www.changxuan.top/?p=1457 引言 JVM 中的 GC 在技术博客中应该算是个老生常谈的话题,网络上也存在着许多质量参差不齐的文章,可以看出来大都是&q ...