DelayQueue有序存储Delayed类型或者子类型的对象,没当从队列中取走元素时,需要等待延迟耗完才会返回该对象。

所谓Delayed类型,因为需要比较,所以继承了Comparable接口:

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

其实Delayed对象的排序和延迟长短是无关的,因为Comparable的compare方法是用户自己实现的,DelayQueue只是保证返回对象的延迟已经耗尽。

DelayQueue需要排序存储Delayed类型的对象同时具备阻塞功能,但是阻塞的过程伴有延迟等待类型的阻塞,因此不能直接使用BlockingPriorityQueue来实现,而是用非阻塞的版本的PriorityQueue来实现排序存储。

private final PriorityQueue<E> q = new PriorityQueue<E>();

因此DelayQueue需要自己实现阻塞的功能(需要一个Condition):

private final Condition available = lock.newCondition();

老规矩还是先来看offer方法:

    public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e);
// 如果原来队列为空,重置leader线程,通知available条件
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}

顺便提一下,因为DelayQueue不限制长度,因此添加元素的时候不会因为队列已满产生阻塞,因此带有超时的offer方法的超时设置是不起作用的:

    public boolean offer(E e, long timeout, TimeUnit unit) {
// 和不带timeout的offer方法一样
return offer(e);
}

因为DelayQueue需要自己实现阻塞,因此关注的重点应该是两个带有阻塞的方法:没有超时的take方法和带有超时的poll方法。

普通poll方法很简单,如果延迟时间没有耗尽的话,直接返回null就可以了。

    public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E first = q.peek();
if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0)
return null;
else
return q.poll();
} finally {
lock.unlock();
}
}

接下来看take和带timeout的poll方法,在看过DelayedWorkQueue之后这部分还是比较好理解的:

    public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
// 如果队列为空,需要等待available条件被通知
E first = q.peek();
if (first == null)
available.await();
else {
long delay = first.getDelay(TimeUnit.NANOSECONDS);
// 如果延迟时间已到,直接返回第一个元素
if (delay <= 0)
return q.poll();
// leader线程存在表示有其他线程在等待,那么当前线程肯定需要等待
else if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
// 如果没有leader线程,设置当前线程为leader线程
// 尝试等待直到延迟时间耗尽(可能提前返回,那么下次
// 循环会继续处理)
try {
available.awaitNanos(delay);
} finally {
// 如果leader线程还是当前线程,重置它用于下一次循环。
// 等待available条件时,锁可能被其他线程占用从而导致
// leader线程被改变,所以要检查
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// 如果没有其他线程在等待,并且队列不为空,通知available条件
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}

再来看带有timeout的poll方法,和DelayedWorkQueue非常相似:

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
if (first == null) {
if (nanos <= 0)
return null;
else
// 尝试等待available条件,记录剩余的时间
nanos = available.awaitNanos(nanos);
} else {
long delay = first.getDelay(TimeUnit.NANOSECONDS);
if (delay <= 0)
return q.poll();
if (nanos <= 0)
return null;
// 当leader线程不为空时(此时delay>=nanos),等待的时间
// 似乎delay更合理,但是nanos也可以,因为排在当前线程前面的
// 其他线程返回时会唤醒available条件从而返回,
// 这里使用nanos和nonas<delay合并更加简单
if (nanos < delay || leader != null)
nanos = available.awaitNanos(nanos);
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
long timeLeft = available.awaitNanos(delay);
// nanos需要更新
nanos -= delay - timeLeft;
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}

前面理解了DelayedWorkQueue再来看DelayQueue就非常容易理解了。

《java.util.concurrent 包源码阅读》20 DelayQueue的更多相关文章

  1. 《java.util.concurrent 包源码阅读》 结束语

    <java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...

  2. 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

    这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...

  3. 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包

    Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...

  4. 《java.util.concurrent 包源码阅读》04 ConcurrentMap

    Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...

  5. 《java.util.concurrent 包源码阅读》17 信号量 Semaphore

    学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...

  6. 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue

    对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...

  7. 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇

    concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...

  8. 《java.util.concurrent 包源码阅读》05 BlockingQueue

    想必大家都很熟悉生产者-消费者队列,生产者负责添加元素到队列,如果队列已满则会进入阻塞状态直到有消费者拿走元素.相反,消费者负责从队列中拿走元素,如果队列为空则会进入阻塞状态直到有生产者添加元素到队列 ...

  9. 《java.util.concurrent 包源码阅读》10 线程池系列之AbstractExecutorService

    AbstractExecutorService对ExecutorService的执行任务类型的方法提供了一个默认实现.这些方法包括submit,invokeAny和InvokeAll. 注意的是来自E ...

随机推荐

  1. Extjs6随笔(终篇)——内容总结

    上个月和Extjs说byebye了,以后大概也没机会用了.之前的博客有点乱,大家看着比较麻烦,所以趁着我还没忘,在这里总结一下♪(^∇^*) 写了个demo,传到git上了,有需要可以自取.Extjs ...

  2. 初学者易上手的SSH-hibernate02 三种查询方式

    在上一章中已经搭建好了一个hibernate的环境,那么这一章我们就使用这个环境来进行基本CRUD.在这之前我们先了解一个东西:主键生成策略.就是当向数据库表中插入记录的时候,这个记录的主键该如何生成 ...

  3. 【前端】windows64位必备软件清单

    目录 一.前言 二.日常必备 三.前端相关 四.个人习惯 一.前言 重做系统以后,安装各种软件就是挺烦人的一件事. 特地整理成文章,并且将相关软件上传到了百度网盘,省的以后再各种找资源了. 百度网盘下 ...

  4. JqueryMobile基础之创建页面

    首先简答介绍一下JQueryMobile吧,我觉得用一句话来讲就是可以 "写更少的代码,做更多的事情" : 它可以通过一个灵活及简单的方式来布局网页,且兼容所有移动设备.这也是我自 ...

  5. 《HelloGitHub》第 19 期

    前言 最近很少写博客了,工作上的事情太多(在做一些数据分析方面的工作,之前是 Web 开发),时间捉襟见肘.更多的时间都花在工作上,没有精力.时间积累整理知识.说来还是能力太差.效率有问题. 后面会好 ...

  6. 为什么C++没有对应realloc的new操作符呢?

    http://blog.csdn.net/rabbit729/article/details/3400260 这个用memcpy明显是有问题的.如果类有资源分配的话,直接memcpy不能复制资源,会导 ...

  7. Django1.11搭建一个简易上传显示图片的后台

    本文为作者原创,转载请注明出处(http://www.cnblogs.com/mar-q/)by 负赑屃 项目展示需要,之前没研究过Django,网上查资料快速做了一个后台,写下了防止自己忘了. p ...

  8. MAC - PhpStorm安装调试环境xdebug

    今天下午一直在捣鼓如何用PhpStorm进行调试,查找了许多资料,零零碎碎的,所以自己弄篇文章记录一下步骤. 安装xdebug 使用brew安装xdebug,语法如下 brew install hom ...

  9. PHP(一)OOP基础

    [面向过程&面向对象] 1.面向过程:专注于解决一件事情的过程.最大的特点,是有一个个函数来实现功能需求 2.面向对象:专注于有哪一个对象来实现这个功能,最大的特点,时产生一个个具有属性和方法 ...

  10. HTML中引入CSS的方法

    在HTML中引入CSS的方法主要有四种,它们分别是行内式.内嵌式.链接式和导入式. 1.行内式 行内式是在标记的style属性中设定CSS样式.这种方式没有体现出CSS的优势,不推荐使用. 2.内嵌式 ...