《java.util.concurrent 包源码阅读》20 DelayQueue
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的更多相关文章
- 《java.util.concurrent 包源码阅读》 结束语
<java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...
- 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分
这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...
- 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包
Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...
- 《java.util.concurrent 包源码阅读》04 ConcurrentMap
Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...
- 《java.util.concurrent 包源码阅读》17 信号量 Semaphore
学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...
- 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue
对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...
- 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇
concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...
- 《java.util.concurrent 包源码阅读》05 BlockingQueue
想必大家都很熟悉生产者-消费者队列,生产者负责添加元素到队列,如果队列已满则会进入阻塞状态直到有消费者拿走元素.相反,消费者负责从队列中拿走元素,如果队列为空则会进入阻塞状态直到有生产者添加元素到队列 ...
- 《java.util.concurrent 包源码阅读》10 线程池系列之AbstractExecutorService
AbstractExecutorService对ExecutorService的执行任务类型的方法提供了一个默认实现.这些方法包括submit,invokeAny和InvokeAll. 注意的是来自E ...
随机推荐
- 为UWP应用开启回环访问权限
最近在项目中遇到UWP调用WCF的需求,考虑到UWP不能寄宿WCF服务(如果能,或者有类似技术,请告知),于是写了一个WPF程序寄宿WCF服务,然后再用UWP调用服务. 写的时候并没有碰到什么问题,直 ...
- Windows历史
1983年11月:Microsoft宣布Windows的第一个版本:以字符为基础 的窗口系统: 1985年11月:Windows1.0: 1990年5月:Windows 3.0(成功版本),16位OS ...
- Linux下PHP连接MS SQLServer的办法
Linux下PHP连接MS SQLServer的办法分析问题 本来PHP脚本读写SQLServer是没有什么问题的,在Apache for windows和Windows IIS下可以工作的很好,一般 ...
- Linux入门(16)——Ubuntu16.04下配置sublime text 3使用markdown
sublime text 3安装两个插件: MarkDown Editing OmniMarkupPreviewer 有的人使用 MarkDown Editing markdownpreviewer ...
- 对ajax请求的简单封装,操作更方便
我这里的接口数据调用的js叫interface.js,接口路径管理的js叫webSiteControl.js /** * Created by l2776 on 2017/7/11. * 接口数据调用 ...
- Python 的装饰器
Python 在语言级别提供了装饰器模式的实现,代码中Python内置的 functools.wraps 会完成包括函数名属性处理替换 #!/usr/bin/env python3 #--coding ...
- 版本控制之三:SVN合并及解决冲突(转)
转自 http://www.cnblogs.com/xiaobaihome/archive/2012/03/20/2408089.html 接下来,试试用TortoiseSVN修改文件,添加文件,删除 ...
- Ubuntu配置OpenStack 二:配置时间同步NTP和安装数据库Maridb以及问题总结
继上一节Ubuntu配置OpenStack 一:配置主机环境,下面继续为安装时间同步,以及配置openstack的安装包源和安装数据库Maridb.(全文截图都是由自己徒手搭建完成并且截图) 一.安装 ...
- 高性能前端框架React详解
前 言 React 是一个用于构建[用户界面]的 JAVASCRIPT 库. React主要用于构建UI,很多人认为 React 是 MVC 中的 V(视图). React 起源于 Facebo ...
- Ignatius and the Princess II
Ignatius and the Princess II Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Jav ...