问题

(1)DelayQueue是阻塞队列吗?

(2)DelayQueue的实现方式?

(3)DelayQueue主要用于什么场景?

简介

DelayQueue是java并发包下的延时阻塞队列,常用于实现定时任务。

继承体系

从继承体系可以看到,DelayQueue实现了BlockingQueue,所以它是一个阻塞队列。

另外,DelayQueue还组合了一个叫做Delayed的接口,DelayQueue中存储的所有元素必须实现Delayed接口。

那么,Delayed是什么呢?

  1. public interface Delayed extends Comparable<Delayed> {
  2. long getDelay(TimeUnit unit);
  3. }

Delayed是一个继承自Comparable的接口,并且定义了一个getDelay()方法,用于表示还有多少时间到期,到期了应返回小于等于0的数值。

源码分析

主要属性

  1. // 用于控制并发的锁
  2. private final transient ReentrantLock lock = new ReentrantLock();
  3. // 优先级队列
  4. private final PriorityQueue<E> q = new PriorityQueue<E>();
  5. // 用于标记当前是否有线程在排队(仅用于取元素时)
  6. private Thread leader = null;
  7. // 条件,用于表示现在是否有可取的元素
  8. private final Condition available = lock.newCondition();

从属性我们可以知道,延时队列主要使用优先级队列来实现,并辅以重入锁和条件来控制并发安全。

因为优先级队列是无界的,所以这里只需要一个条件就可以了。

还记得优先级队列吗?点击链接直达【死磕 java集合之PriorityQueue源码分析

主要构造方法

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

构造方法比较简单,一个默认构造方法,一个初始化添加集合c中所有元素的构造方法。

入队

因为DelayQueue是阻塞队列,且优先级队列是无界的,所以入队不会阻塞不会超时,因此它的四个入队方法是一样的。

  1. public boolean add(E e) {
  2. return offer(e);
  3. }
  4. public void put(E e) {
  5. offer(e);
  6. }
  7. public boolean offer(E e, long timeout, TimeUnit unit) {
  8. return offer(e);
  9. }
  10. public boolean offer(E e) {
  11. final ReentrantLock lock = this.lock;
  12. lock.lock();
  13. try {
  14. q.offer(e);
  15. if (q.peek() == e) {
  16. leader = null;
  17. available.signal();
  18. }
  19. return true;
  20. } finally {
  21. lock.unlock();
  22. }
  23. }

入队方法比较简单:

(1)加锁;

(2)添加元素到优先级队列中;

(3)如果添加的元素是堆顶元素,就把leader置为空,并唤醒等待在条件available上的线程;

(4)解锁;

出队

因为DelayQueue是阻塞队列,所以它的出队有四个不同的方法,有抛出异常的,有阻塞的,有不阻塞的,有超时的。

我们这里主要分析两个,poll()和take()方法。

  1. public E poll() {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. E first = q.peek();
  6. if (first == null || first.getDelay(NANOSECONDS) > 0)
  7. return null;
  8. else
  9. return q.poll();
  10. } finally {
  11. lock.unlock();
  12. }
  13. }

poll()方法比较简单:

(1)加锁;

(2)检查第一个元素,如果为空或者还没到期,就返回null;

(3)如果第一个元素到期了就调用优先级队列的poll()弹出第一个元素;

(4)解锁。

  1. public E take() throws InterruptedException {
  2. final ReentrantLock lock = this.lock;
  3. lock.lockInterruptibly();
  4. try {
  5. for (;;) {
  6. // 堆顶元素
  7. E first = q.peek();
  8. // 如果堆顶元素为空,说明队列中还没有元素,直接阻塞等待
  9. if (first == null)
  10. available.await();
  11. else {
  12. // 堆顶元素的到期时间
  13. long delay = first.getDelay(NANOSECONDS);
  14. // 如果小于0说明已到期,直接调用poll()方法弹出堆顶元素
  15. if (delay <= 0)
  16. return q.poll();
  17. // 如果delay大于0 ,则下面要阻塞了
  18. // 将first置为空方便gc,因为有可能其它元素弹出了这个元素
  19. // 这里还持有着引用不会被清理
  20. first = null; // don't retain ref while waiting
  21. // 如果前面有其它线程在等待,直接进入等待
  22. if (leader != null)
  23. available.await();
  24. else {
  25. // 如果leader为null,把当前线程赋值给它
  26. Thread thisThread = Thread.currentThread();
  27. leader = thisThread;
  28. try {
  29. // 等待delay时间后自动醒过来
  30. // 醒过来后把leader置空并重新进入循环判断堆顶元素是否到期
  31. // 这里即使醒过来后也不一定能获取到元素
  32. // 因为有可能其它线程先一步获取了锁并弹出了堆顶元素
  33. // 条件锁的唤醒分成两步,先从Condition的队列里出队
  34. // 再入队到AQS的队列中,当其它线程调用LockSupport.unpark(t)的时候才会真正唤醒
  35. // 关于AQS我们后面会讲的^^
  36. available.awaitNanos(delay);
  37. } finally {
  38. // 如果leader还是当前线程就把它置为空,让其它线程有机会获取元素
  39. if (leader == thisThread)
  40. leader = null;
  41. }
  42. }
  43. }
  44. }
  45. } finally {
  46. // 成功出队后,如果leader为空且堆顶还有元素,就唤醒下一个等待的线程
  47. if (leader == null && q.peek() != null)
  48. // signal()只是把等待的线程放到AQS的队列里面,并不是真正的唤醒
  49. available.signal();
  50. // 解锁,这才是真正的唤醒
  51. lock.unlock();
  52. }
  53. }

take()方法稍微要复杂一些:

(1)加锁;

(2)判断堆顶元素是否为空,为空的话直接阻塞等待;

(3)判断堆顶元素是否到期,到期了直接调用优先级队列的poll()弹出元素;

(4)没到期,再判断前面是否有其它线程在等待,有则直接等待;

(5)前面没有其它线程在等待,则把自己当作第一个线程等待delay时间后唤醒,再尝试获取元素;

(6)获取到元素之后再唤醒下一个等待的线程;

(7)解锁;

使用方法

说了那么多,是不是还是不知道怎么用呢?那怎么能行,请看下面的案例:

  1. public class DelayQueueTest {
  2. public static void main(String[] args) {
  3. DelayQueue<Message> queue = new DelayQueue<>();
  4. long now = System.currentTimeMillis();
  5. // 启动一个线程从队列中取元素
  6. new Thread(()->{
  7. while (true) {
  8. try {
  9. // 将依次打印1000,2000,5000,7000,8000
  10. System.out.println(queue.take().deadline - now);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }).start();
  16. // 添加5个元素到队列中
  17. queue.add(new Message(now + 5000));
  18. queue.add(new Message(now + 8000));
  19. queue.add(new Message(now + 2000));
  20. queue.add(new Message(now + 1000));
  21. queue.add(new Message(now + 7000));
  22. }
  23. }
  24. class Message implements Delayed {
  25. long deadline;
  26. public Message(long deadline) {
  27. this.deadline = deadline;
  28. }
  29. @Override
  30. public long getDelay(TimeUnit unit) {
  31. return deadline - System.currentTimeMillis();
  32. }
  33. @Override
  34. public int compareTo(Delayed o) {
  35. return (int) (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
  36. }
  37. @Override
  38. public String toString() {
  39. return String.valueOf(deadline);
  40. }
  41. }

是不是很简单,越早到期的元素越先出队。

总结

(1)DelayQueue是阻塞队列;

(2)DelayQueue内部存储结构使用优先级队列;

(3)DelayQueue使用重入锁和条件来控制并发安全;

(4)DelayQueue常用于定时任务;

彩蛋

java中的线程池实现定时任务是直接用的DelayQueue吗?

当然不是,ScheduledThreadPoolExecutor中使用的是它自己定义的内部类DelayedWorkQueue,其实里面的实现逻辑基本都是一样的,只不过DelayedWorkQueue里面没有使用现成的PriorityQueue,而是使用数组又实现了一遍优先级队列,本质上没有什么区别。


欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java集合之DelayQueue源码分析的更多相关文章

  1. 死磕 java集合之PriorityBlockingQueue源码分析

    问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...

  2. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  3. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

  4. 死磕 java集合之LinkedHashSet源码分析

    问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...

  5. 死磕 java集合之ConcurrentHashMap源码分析(三)

    本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...

  6. 死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  7. 【死磕 Java 集合】— ConcurrentSkipListMap源码分析

    转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...

  8. 死磕 java集合之LinkedList源码分析

    问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...

  9. 死磕 java集合之ConcurrentSkipListSet源码分析——Set大汇总

    问题 (1)ConcurrentSkipListSet的底层是ConcurrentSkipListMap吗? (2)ConcurrentSkipListSet是线程安全的吗? (3)Concurren ...

随机推荐

  1. ubuntu11.04安装nginx+php+mysql

    先列参考内容,后面我再补充点东西: http://www.4wei.cn/archives/1001436 http://www.gidot.net/blog/article.asp?id=322 上 ...

  2. Mysql 快速指南

    Mysql 快速指南 本文的示例在 Mysql 5.7 下都可以测试通过. 知识点 概念 数据库(database):保存有组织的数据的容器(通常是一个文件或一组文件). 数据表(table):某种特 ...

  3. Scrapy爬虫框架第四讲(Linux环境)

    下面我们来学习Selector的具体使用:(参考文档:http://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/selectors.html) Selecto ...

  4. 前端leader找我谈心:我是如何从刚毕业的前端菜鸟一步步成长为前端架构师的?

    谈谈学习 我做前端已经有五年的时间了,从大学刚毕业的时候,我是一个完全什么都不懂的小白.虽然我大学里学的是软件工程专业,但是因为在大学里荒废学业,每天只知道打游戏,基本上到大学毕业之前我是什么都不会的 ...

  5. 用CSS画小猪佩奇,你就是下一个社会人!

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 作者:江志耿 | 腾讯TEG网络工程师 我是佩奇,哼,这是我的弟弟乔治,呱呱,这是我的妈妈,嚯,这是我的爸爸,嚯~ 背景 小猪佩奇已经火了好 ...

  6. Java JFrame图形界面 ----一个简单的窗口

    #开始 申请博客已经有一段时间了 但是一直没有时间写博文(其实还是懒虫侵蚀了大脑) 最近正在学习JFrame做窗口 遇到了很多的问题 为了解决问题也谋杀了很多的脑细胞 为了让更多的朋友不死的很多脑细胞 ...

  7. Graphviz--图形绘制工具

    可以试试ProcessOn.com, 一个专业在线画流程图的工具,使用Chrome&Firefox浏览器,支持快捷键,非常方便.快捷键如下:Ctrl+A 全选,当移动整张图时非常方便Ctrl+ ...

  8. 安装vmtools之后任然不能在虚拟机和主机之间复制粘贴的问题

    安装vmtools之后任然不能在虚拟机和主机之间复制粘贴的问题 都是因为这个进程没有启动起来,你只需要在启动后在终端输入 "/usr/bin /vmware-user" 就可以手动 ...

  9. Java 读书笔记 (八) 修饰符

    Java语言提供了很多修饰符,主要分为以下两类: 访问修饰符 非访问修饰符 访问控制修饰符 default (即缺省,什么也不写): 在同一包内可见,不使用任何修饰符.使用对象.类.接口.变量.方法. ...

  10. 开机出现loading (hd0)/ntldr。。。

    电脑一开机就出现ntldr is missing的原因:1.操作系统文件损坏.2.MBR表损坏.3.硬盘数据线松了.4.硬盘坏了.解决方法:1.重新安装操作系统.2.用U盘或光盘引导,进入PE系统,用 ...