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. sql server 2008 18456错误

    来自:http://blog.csdn.net/qishuangquan/article/details/6024767 百度搜18456错误几乎只能搜到一篇文章,并不是说结果条数,而是所有的文章都是 ...

  2. ubuntu下codeblocks安装与中文化

    什么是Code::Blocks Code::Blocks是一个免费.开源.跨平台的集成开发环境,使用C++开发,并且使用wxWidgets做为GUI库.Code::Blocks使用了插件架构,其功能可 ...

  3. 逆向知识第一讲,IDA的熟悉使用,以及TEB,PEB结构

    逆向知识第一讲,IDA的熟悉使用,以及TEB,PEB结构 一丶熟悉IDA,以及手工制作sig文件. IDA,静态分析工具,网上随便找一个即可下载. 首先,我们写一个可执行EXE,最简单的 使用IDA打 ...

  4. SpringBoot的几个使用技巧

    SpringBoot的几个使用技巧 首先提供几个SpringBoot开发过程中常用的网站: Spring Boot官方文档:http://docs.spring.io/spring-boot/docs ...

  5. Java基础总结--数组

    ---数组的定义---组织存储一组数据1.存放相同类型数据的集合--就是一种容器本质上变量也是一种容器--区别就是只存储了一个数据的容器--面对容器,而不是分散的数据eg.买一盘鸡蛋--蛋托其实就是容 ...

  6. C++const使用(06)

    可以在类中使用const关键字定义数据成员和成员函数或修饰一个对象.一个const对象只能访问const成员函数,否则将产生编译错误. 常量成员 常量成员包括常量数据成员.静态常数据成员和常引用.静态 ...

  7. swift 之设计模式 适配器

    大学的时候,有一个<近世代数>的教授,他上课从来不看课本,并且也不按课本来讲解课程,但是他讲的东西比书本深刻形象(幽默,口才杠杠的),有层次感,除了授业,他还经常给我讲一些为人处世,做学问 ...

  8. django-rest-framework之序列化

    前言:昨天学习了rest-framework序列化方面的知识,故写了博客记录一下.官网:http://www.django-rest-framework.org/tutorial/1-serializ ...

  9. css是如何实现在页面文字不换行、自动换行、强制换行的

    强制不换行 div{ white-space:nowrap; } 自动换行 div{ word-wrap: break-word; word-break: normal; } 强制英文单词断行 div ...

  10. HDU 6143 Killer Names

    Killer Names Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Tota ...