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. D - The Frog's Games (二分)

    The annual Games in frogs' kingdom started again. The most famous game is the Ironfrog Triathlon. On ...

  3. 1.4.17 base标签

    如果我们定义的超链接在另一个窗口打开,代码如下: <!DOCTYPE html> <html lang="en"> <head> <met ...

  4. adb、adb shell am、adb shell pm命令的详细使用说明

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/64183248 1.在命令行终端执行下面的命令: adb >adb.txt 2 ...

  5. DVWA之SQL Injection

    SQL Injection SQL Injection,即SQL注入,是指攻击者通过注入恶意的SQL命令,破坏SQL查询语句的结构,从而达到执行恶意SQL语句的目的.SQL注入漏洞的危害是巨大的,常常 ...

  6. 【编译原理】求First和Follow

    写这篇博客的原因,是因为考试前以为自己已经将这个问题弄清楚了,但是,考试的时候,发现自己还是不会,特别是求follow集合.虽然考试结束了,希望屏幕前的你,可以真正理解这个问题. 码字和做视频都不容易 ...

  7. Yii2访问gii模块403

    出现问题 访问Yii2的gii模块没有权限,403 找到原因 在Yii2-gii源码文件中(vendor/yiisoft/yii2-gii/src/Module.php)可以看到有一个配置项$allo ...

  8. 通过LinkedHashMap实现LRU算法

    一.基于LinkedHashMap源码分析 方法调用流程(这里只是以put方法位例) put() -> putVal() -> afterNodeInsertion() -> rem ...

  9. 『动善时』JMeter基础 — 15、使用JMeter实现上传文件

    目录 1.用于演示的项目说明 2.测试计划内包含的元件 3.HTTP请求界面内容 4.查看结果 5.总结 6.补充:MIME类型简介 (1)MIME说明 (2)常见类型 在上一篇文章[使用JMeter ...

  10. 痞子衡嵌入式:MCUBootUtility v3.3发布,可配合SBL项目使用

    -- 痞子衡维护的NXP-MCUBootUtility工具距离上一个大版本(v3.0.0)发布过去4个多月了,期间痞子衡其实断断续续做个几个小版本更新,这一次痞子衡为大家带来了稳定版本v3.3.0,顺 ...