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. Codeforces Global Round 14 E. Phoenix and Computers

    题目链接 点我跳转 题目大意 给定 \(N\) 台电脑,起初每台电脑都是关闭的 现在你可以随意打开电脑,但如果第 \(i-1\).第 \(i+1\) 台电脑是开启的,则第 \(i\) 台电脑也会自动开 ...

  2. Laravel 队列功能 简单应用

    生成任务类 默认情况下,应用程序的所有可排队任务都存储在 app/Jobs 目录下.如果 app/Jobs 目录不存在,则会在运行 make:job Artisan 命令时将创建它.你可以使用 Art ...

  3. 怎样用SQL修改某个字段的部分内容

    方法:update dede_addonarticle set body = replace(body,'#p#分页标题#e#',' ') where body like '%#p#分页标题#e#%'

  4. hdu3870 基于最短路的最小割

    题意:      给你一个平面图,让你输出(1,1),(n ,n)的最小割.. 思路:       看完题想都没想直接最大流,结果TLE,想想也是 G<400*400,400*400*4> ...

  5. dependencyManagement 版本管理

    帮别人解决bug的时候碰到,随便记录一下. 使用<dependencyManagement>标签, 做版本管理的时候,需要父子项目关联,就需要子模块中利用<parent> 否则 ...

  6. idea插件手动安装

    更多精彩: 例如安装Grep Console 插件  把刚才解压的文件放到  plugins   重启idea   自定义设计

  7. Java 给Word添加数字签名

    本文以Java程序代码为例,介绍如何给Word文档添加数字签名. 程序运行环境 IntedliJ IDEA JDK 1.8.0 Jar包:spire.doc.jar 4.5.1 Word文档:.doc ...

  8. 一行代码解决JS数字大于2^53精度错误的问题

    服务端使用长整型(Int64)的数字,在浏览器端使用JS的number类型接收时,当这个实际值超过 (2^53-1)时,JS变量的值和实际值就会出现不相等的问题.常见场景比如使用雪花算法生成Id. 在 ...

  9. Alpha事后分析

    设想和目标 1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 我们的软件的功能主要是让一些基于表单识别的项目(如微软智能表单识别项目)减少在数据生成方面上浪费的 ...

  10. [Java] 静态代理 动态代理

    原理 静态代理=代理模式 动态代理=代理模式+反射 代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强 代理类和被代理类应该共同实现一个接口,或者是共同继承某个类 静态代 ...