DelayQueue 是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue 来实现。队列中的元素必须实现Delayed 接口,在创建元素时可以指定多久才能从队列中获取当前元素。 只有在延迟期满时才能从队列中提取元素。

DelayQueue 非常有用,可以将 DelayQueue 运用在以下应用场景。

  • 缓存系统的设计:可以用 DelayQueue 保存缓存元素的有效期,使用一个线程循环查询 DelayQueue,一旦能从DelayQueue 中获取元素时,表示缓存有效期到了。
  • 定时任务调度:使用 DelayQueue 保存当天将会执行的任务和执行时间,一旦从 DelayQueue 中获取到任务就开始执行,比如 TimerQueue 就是使用 DelayQueue 实现的。

1.实现 Delay 接口

 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);}

需要实现两个方法:

  • comparTo(Delayed o):让延时时间最长的放在队列的末尾(即最早可以获取的元素放在队首)。
  • getDelay(TimeUnit unit):返回剩余延迟时间,返回负数则说明已经过了延迟时间,可以获取元素。

2.延迟队列原理

take 方法片段:

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 <= 0)return q.poll(); //过了延迟时间后队首元素出队,执行最外层的finally代码块唤醒其他等待线程并解锁first = null; // 消除对队首元素的强引用,方便GCif (leader != null)//leader是正在等待队首元素的线程,leader不空说明有线程正在等待队首元素,可以使用无参的wait一直等待,leader线程返回后会唤醒wait的线程available.await();else {//如果没有线程正在等待队首元素,则当前线程进行等待(leader设为当前线程)Thread thisThread = Thread.currentThread();leader = thisThread;//使用Condition的超时等待机制try {available.awaitNanos(delay);} finally {if (leader == thisThread)leader = null;}}}}} finally {if (leader == null && q.peek() != null)available.signal();lock.unlock();}
}

线程在 take() 方法中有两种类型的等待:

  • 延迟时间没有到,且没有其他线程在等待队首元素。

    • 这时会使用超时等待的机制:available.awaitNanos(delay)。如果在没有其他线程在等待队首元素时,使用 available.await() 进行等待的话,会进入一直等待的状态,因为没有其他线程调用 available.signal()。对应上面源码 17 行
  • 延迟时间没有到,且有其他线程正在等待队首元素。
    • 如果有其他线程在等待队首元素,从以上源码可以看出,这个线程一定是使用 available.awaitNanos(delay) 进行等待的,所以这个线程最终会调用 available.signal() 来唤醒等待队列上的线程,因此这种情况使用 available.await() 进行等待,对应上面源码 12 行。

由以上两点可以总结出多线程情况下竞争使用 DelayQueue 的特点:

  1. 一个元素(即队首元素)只能由一个线程进行超时等待(getDelay() 方法可以获取剩余等待时间),此时其他线程进入 take() 方法后会调用 available.await() 一直等待下去,正在等待队首元素的那个线程返回后会调用 available.signal() 唤醒等待队列中的进行。这种做法的言外之意就是一个元素只能被一个线程获取。
  2. 从上面源码最外层的 finally 代码块可以看出,每个线程在 take() 方法退出后都会调用 available.signal()。

3.使用 DelayQueue 实现简易缓存

思路:使用 Map 来维护缓存数据,DelayQueue 保存 Map 的键,另外开启一个线程,循环获取 DelayQueue 中保存的键,当获取到这个键时,说明缓存时间已经到了,则根据这个键把 Map 中的数据 remove 掉。

public class Cache<K, V> implements Runnable {private Map<K, V> map = new ConcurrentHashMap<>(); //使用线程安全的ConcurrentHashMapprivate DelayQueue<DelayItem<K>> delayQueue = new DelayQueue<>();private volatile boolean stop = false; //多线程环境下,状态需要使用 volatile 修饰,保证其他线程修改状态后,状态的最新值能够立即被其他线程读取。public Cache() {// 开启一个线程循环获取 DelayQueue 中保存的键,获取到时说明缓存过期。new Thread(this).start();}public void add(K key, V value, long expire) {map.put(key, value);delayQueue.add(new DelayItem<>(expire, key));}public V get(Object key) {return map.get(key);}@Overridepublic void run() {while (!stop) {DelayItem<K> item = null;try {item = delayQueue.take();} catch (InterruptedException e) {e.printStackTrace();}if (item != null) {map.remove(item.getKey()); //获取到元素说明缓存过期,需要 remove 掉。}}}public void shutdown() {stop = false;}static class DelayItem<K> implements Delayed {private long createTime;private long expire;private K key;public DelayItem(long expire, K key) {this.createTime = System.currentTimeMillis();this.expire = expire;this.key = key;}public K getKey() {return key;}@Overridepublic long getDelay(TimeUnit unit) {return expire - unit.MILLISECONDS.toSeconds(System.currentTimeMillis() - createTime);}// 按延迟时间排序,延迟短的在前@Overridepublic int compareTo(Delayed o) {return this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS) == 0 ?0 : this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS) < 0 ? -1 : 1;}}}

DelayQueue原理与使用相关推荐

  1. DelayQueue详解

    一.DelayQueue是什么 DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走.这种队列是有序的,即队头对 ...

  2. DelayQueue是什么

    一.DelayQueue是什么 DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走.这种队列是有序的,即队头对 ...

  3. DelayQueue实例和源码解析

    个人思考: 当时最开始的时候,想到的是通过直接sleep(60),然后push到redis队列,然后redis的blpop,但是这样会导致线程阻塞, DelayQueue相当于把这么多的线程阻塞放到了 ...

  4. Java并发编程之线程定时器ScheduledThreadPoolExecutor解析

    定时器 就是需要周期性的执行任务,也叫调度任务,在JDK中有个类Timer是支持周期性执行,但是这个类不建议使用了. ScheduledThreadPoolExecutor 继承自ThreadPool ...

  5. 浅析 Queue 和 Deque

    终于开始了 LeetCode 的练习,看到 102. 二叉树的层序遍历 有种解法利用到了队列,想着挨个看看基础队列中的方法,便有了这篇文章. 基于 Java 对 Queue 以及 Deque(doub ...

  6. 《Java并发编程之美》

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yAwEsYPZ-1661534116043)(img/\1625573175405.jpg)] [外链图片转存失败,源站 ...

  7. Java 并发编程-不懂原理多吃亏(送书福利)

    作者 | 加多 关注阿里巴巴云原生公众号,后台回复关键字"并发",即可参与送书抽奖! ** 导读:并发编程与 Java 中其他知识点相比较而言学习门槛较高,从而导致很多人望而却步. ...

  8. Java DelayQueue延迟队列的使用和源码分析

    文章目录 概述 示例 原理分析 概述 DelayQueue 是JAVA提供的延时队列,队列内部的对象必须实现 Delayed 接口,该接口只有一个 getDelay 方法,返回延迟执行的时长. pub ...

  9. java线程池_Java多线程并发:线程基本方法+线程池原理+阻塞队列原理技术分享...

    线程基本方法有哪些? 线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等. 线程等待(wait) 调用该方法的线程进入 WAITING 状态,只有等 ...

最新文章

  1. Oracle RAC删除节点
  2. 关于Euclid算法
  3. APICS与AX的Master Planning(二)--Rescheduling Assumption 重排假设
  4. ios 带scrollView的控制器,双击“状态栏”,返回scrollView的顶部
  5. Activiti工作流之业务标识和流程的挂起激活
  6. python中不区分大小写语句怎么写_Python中的标识符不区分大小写。( )_学小易找答案...
  7. C++ explicit 的用法,就是必须显示调用
  8. TensorFlow CIFAR-10数据集
  9. H5的Websocket基本使用
  10. 仍然不安全:变成了Java 9功能的Java 6中的主要错误
  11. [ECMAScript] es6对函数做了哪些优化?
  12. python写前端和js_Python之路【第十二篇】前端之jsdomejQuery
  13. 深入浅出的排序算法-冒泡排序
  14. 小白学深度之LSTM长短期记忆神经网络——深度AI科普团队
  15. 最全面的Linux命令大全出炉了
  16. LPC1788 nand驱动
  17. 项目发布到Tomcat后,网页图片不显示
  18. JDK8绿色安装详细步骤
  19. pdf文件展示盖章及下载
  20. Aspose实现word转图片、pdf

热门文章

  1. pdf中如何编辑文本框
  2. 杭州居住证到期后如何续期
  3. iOS中gif图片的分解与显示
  4. 二进制数字调制器的设计
  5. NC单据模板控制公式的使用
  6. 树莓派基础实验33:TCRT5000红外循迹传感器实验
  7. 扑克牌之斗地主的简单代码
  8. CRM应用:CRM与DRP
  9. Anaconda环境GDAL库基于whl文件的配置方法
  10. Java基础公元纪年法换算天干地支纪年法(趣味)