【Java并发编程】19、DelayQueue源码分析
DelayQueue,带有延迟元素的线程安全队列,当非阻塞从队列中获取元素时,返回最早达到延迟时间的元素,或空(没有元素达到延迟时间)。DelayQueue的泛型参数需要实现Delayed接口,Delayed接口继承了Comparable接口,DelayQueue内部使用非线程安全的优先队列(PriorityQueue),并使用Leader/Followers模式,最小化不必要的等待时间。DelayQueue不允许包含null元素。
领导者/追随者模式是多个工作线程轮流获得事件源集合,轮流监听、分发并处理事件的一种模式。在任意时间点,程序都仅有一个领导者线程,它负责监听IO事件。而其他线程都是追随者,它们休眠在线程池中等待成为新的领导者。当前的领导者如果检测到IO事件,首先要从线程池中推选出新的领导者线程,然后处理IO事件。此时,新的领导者等待新的IO事件,而原来的领导者则处理IO事件,二者实现了并发。
简单理解,就是最多只有一个线程在处理,其他线程在睡眠。在DelayQueue的实现中,Leader/Followers模式用于等待队首的第一个元素。
类定义及参数:

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {
/** 重入锁,实现线程安全 */
private final transient ReentrantLock lock = new ReentrantLock();
/** 使用优先队列实现 */
private final PriorityQueue<E> q = new PriorityQueue<E>(); /** Leader/Followers模式 */
private Thread leader = null; /** 条件对象,当新元素到达,或新线程可能需要成为leader时被通知 */
private final Condition available = lock.newCondition();

构造函数:

/**
* 默认构造,得到空的延迟队列
*/
public DelayQueue() {} /**
* 构造延迟队列,初始包含c中的元素
*
* @param c 初始包含的元素集合
* @throws NullPointerException 当集合或集合任一元素为空时抛出空指针错误
*/
public DelayQueue(Collection<? extends E> c) {
this.addAll(c);
}

add方法:

/**
* 向延迟队列插入元素
*
* @param e 要插入的元素
* @return true
* @throws NullPointerException 元素为空,抛出空指针错误
*/
public boolean add(E e) {
// 直接调用offer并返回
return offer(e);
}

offer方法:

/**
* 向延迟队列插入元素
*
* @param e 要插入的元素
* @return true
* @throws NullPointerException 元素为空,抛出空指针错误
*/
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
// 获得锁
lock.lock();
try {
// 向优先队列插入元素
q.offer(e);
// 若在此之前队列为空,则置空leader,并通知条件对象,需要结合take方法看
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
// 释放锁
lock.unlock();
}
}

put方法:

/**
* 向延迟队列插入元素. 因为队列是无界的,所以不会阻塞。
*
* @param e 要插入的元素
* @throws NullPointerException 元素为空,抛出空指针错误
*/
public void put(E e) {
offer(e);
}

带超时的offer方法:

/**
* 向延迟队列插入元素. 因为队列是无界的,所以不会阻塞,因此,直接调用offer方法并返回
*
* @param e 要插入的元素
* @param timeout 不会阻塞,忽略
* @param unit 不会阻塞,忽略
* @return true
* @throws NullPointerException 元素为空,抛出空指针错误
*/
public boolean offer(E e, long timeout, TimeUnit unit) {
// 直接调用offer方法并返回
return offer(e);
}

poll方法:

/**
* 获取并移除队首的元素, 或者返回null(如果队列不包含到达延迟时间的元素)
*
* @return 队首的元素, 或者null(如果队列不包含到达延迟时间的元素)
*/
public E poll() {
final ReentrantLock lock = this.lock;
// 获得锁
lock.lock();
try {
// 获取优先队列队首元素
E first = q.peek();
// 若优先队列队首元素为空,或者还没达到延迟时间,返回null
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
// 否则,返回并移除队首元素
else
return q.poll();
} finally {
// 释放锁
lock.unlock();
}
}

take方法(重要):

/**
* 获取并移除队首元素,该方法将阻塞,直到队列中包含达到延迟时间的元素
*
* @return 队首元素
* @throws InterruptedException 阻塞时被打断,抛出打断异常
*/
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
// 获得锁,该锁可被打断
lock.lockInterruptibly();
try {
// 循环处理
for (;;) {
// 获取队首元素
E first = q.peek();
// 若元素为空,等待条件,在offer方法中会调用条件对象的通知方法
// 并重新进入循环
if (first == null)
available.await();
// 若元素不为空
else {
// 获取延迟时间
long delay = first.getDelay(NANOSECONDS);
// 若达到延迟时间,返回并移除队首元素
if (delay <= 0)
return q.poll();
// 否则,需要进入等待
first = null; // 在等待时,不持有引用
// 若leader不为空,等待条件
if (leader != null)
available.await();
// 否则,设置leader为当前线程,并超时等待延迟时间
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// 通知其他线程条件得到满足
if (leader == null && q.peek() != null)
available.signal();
// 释放锁
lock.unlock();
}
}

带超时的poll方法(重要):

/**
* 获取并移除队首元素,该方法将阻塞,直到队列中包含达到延迟时间的元素或超时
*
* @return 队首元素,或者null
* @throws InterruptedException 阻塞等待时被打断,抛出打断异常*/
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
nanos = available.awaitNanos(nanos);
} else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return q.poll();
if (nanos <= 0)
return null;
first = null; // don't retain ref while waiting
if (nanos < delay || leader != null)
nanos = available.awaitNanos(nanos);
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
long timeLeft = available.awaitNanos(delay);
nanos -= delay - timeLeft;
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}

peek方法:

/**
* 获取但不移除队首元素,或返回null(如果队列为空)。和poll方法不同,
* 若队列不为空,该方法换回队首元素,不论是否达到延迟时间
*
* @return 队首元素,或null(如果队列为空)
*/
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return q.peek();
} finally {
lock.unlock();
}
}

出处:
https://www.cnblogs.com/enumhack/p/7472873.html
https://www.cnblogs.com/wanly3643/p/3944661.html
jdk源码
【Java并发编程】19、DelayQueue源码分析的更多相关文章
- Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式
在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...
- Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式
通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...
- Java并发系列[5]----ReentrantLock源码分析
在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...
- 多线程高并发编程(3) -- ReentrantLock源码分析AQS
背景: AbstractQueuedSynchronizer(AQS) public abstract class AbstractQueuedSynchronizer extends Abstrac ...
- Java并发编程之ThreadLocal源码分析
## 1 一句话概括ThreadLocal<font face="微软雅黑" size=4> 什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象 ...
- Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析
学习Java并发编程不得不去了解一下java.util.concurrent这个包,这个包下面有许多我们经常用到的并发工具类,例如:ReentrantLock, CountDownLatch, Cyc ...
- 多线程高并发编程(10) -- ConcurrentHashMap源码分析
一.背景 前文讲了HashMap的源码分析,从中可以看到下面的问题: HashMap的put/remove方法不是线程安全的,如果在多线程并发环境下,使用synchronized进行加锁,会导致效率低 ...
- Java并发编程之ReentrantLock源码分析
ReentrantLock介绍 从JDK1.5之前,我们都是使用synchronized关键字来对代码块加锁,在JDK1.5引入了ReentrantLock锁.synchronized关键字性能比Re ...
- Java并发系列[4]----AbstractQueuedSynchronizer源码分析之条件队列
通过前面三篇的分析,我们深入了解了AbstractQueuedSynchronizer的内部结构和一些设计理念,知道了AbstractQueuedSynchronizer内部维护了一个同步状态和两个排 ...
- Java并发系列[6]----Semaphore源码分析
Semaphore(信号量)是JUC包中比较常用到的一个类,它是AQS共享模式的一个应用,可以允许多个线程同时对共享资源进行操作,并且可以有效的控制并发数,利用它可以很好的实现流量控制.Semapho ...
随机推荐
- 准备在electron上用vue,结果卡在了sqlite3
vue.js看书有一段时间了,也准备动手做一个electron的程序.目录似乎有两种方式搭建方式 一种是使用vue.cli构建工具,就是所谓的脚手架,分别安装vue和electron https:// ...
- 修改VS 中的代码编辑颜色-Vs主题修改
有个性的开发人员总是喜欢使用属于的主题和配色方案,它们可以看出开发者的个性,更改它们可以缓解审美疲劳,总之选择一个适合自己的解决方案可能极大的增加自己的编码舒适度. 1. 配色方案的选择和使用 手动修 ...
- Altera 在线资源使用
Altera 在线资源使用 Altera 在线资源使用 1 1.Altera中文版 2 2.建立myaltera账户 获取官网信息与支持 2 3系统化的设计资源 2 3.1.设计实例 2 3.2.参考 ...
- 文件描述符fd、文件指针fp和vfork()
1. fd:在形式上是一个非负整数.实际上他是一个索引值.指向kernal为每一个进程所维护的该进程打开文件的记录表. 当程序打开一个文件或者创建一个新文件的时候kernal向进程返回一个文件描述符. ...
- [UWP]使用Popup构建UWP Picker
在上一篇博文<[UWP]不那么好用的ContentDialog>中我们讲到了ContentDialog在复杂场景下使用的几个令人头疼的弊端.那么,就让我们在这篇博文里开始愉快的造轮子之旅吧 ...
- Mac键盘按键符号
通过修饰键来查看 像command.option.control.shift这些按键在Mac下叫做修饰键,一般情况下他们大都用来辅助输入或者用作快捷键的修饰键. 打开“系统偏好设置”,点击“键盘→键盘 ...
- 【设计经验】1、Verilog中如何规范的处理inout信号
在FPGA的设计过程中,有时候会遇到双向信号(既能作为输出,也能作为输入的信号叫双向信号).比如,IIC总线中的SDA信号就是一个双向信号,QSPI Flash的四线操作的时候四根信号线均为双向信号. ...
- 770. Basic Calculator IV
Given an expression such as expression = "e + 8 - a + 5" and an evaluation map such as {&q ...
- JS应用实例6:二级联动
本案例很常用,应用场景:注册页面填写籍贯,省市二级联动 总体思想:创建一个二维数组存入省市,获取选中的省份并比较,创建标签遍历添加 代码: <!DOCTYPE html> <html ...
- Linux - 修改内核启动顺序及删除无用内核
现象: CentOS7开机启动界面显示多个内核选项 原因: 正常情况下,有两个启动项,一个是"正常启动",另一个是"救援模式启动"(rescue). 如果启动项 ...