【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 ...
随机推荐
- Android -Services 使用简介
Android Services 四大组件之一,主要用于后台长时间运行.没有界面.这里讲解两种services的启动还有AIDL通信方式. 1.startservices a.建立继承services ...
- cobbler学习
note.youdao.com/share/?id=2f8383d6e9824929012b041f069da26e&type=note#/ IPADDR=192.168.86.4 TYPE= ...
- Alpha 冲刺(1)
Alpha 冲刺 (1/10) Part.1 开篇 队名:彳艮彳亍团队 组长博客:戳我进入 作业博客:班级博客本次作业的链接 Part.2 成员汇报 组员1(组长)柯奇豪 过去两天完成了哪些任务 ...
- MyBatis 源码分析系列文章导读
1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...
- DWARF 中的 Debug Info 格式
本周花了几天的时间来研究怎么在 breakpad [1, 2] 中加入打印函数参数的功能,以期其产生的 callstack 更具可读性,方便定位崩溃原因. 现代 ELF 中的调试信息基本是以 DWAR ...
- ReactNative 深拷贝
1: 导入 import _ from 'lodash' 2: _.cloneDeep(obj)
- MySQL程序端启动密码错误解决方法
MySQL程序端启动密码错误解决方法 一般启动MySQL程序端,都是用mysql -uroot -p命令,当然前提是你的环境变量已经配好了. 为了连接服务器,当调用mysql时,通常需要提供一个MyS ...
- U-boot中SPL功能和源码流程分析
在U-boot目录下,有个比较重要的目录就是SPL的,SPL到底是什么呢?为什么要用它呢? SPL(Secondary programloader)是uboot第一阶段执行的代码.主要负责搬移uboo ...
- Java Web(三) 会话机制,Cookie和Session详解(转载)
https://www.cnblogs.com/whgk/p/6422391.html 很大一部分应该知道什么是会话机制,也能说的出几句,我也大概了解一点,但是学了之后几天不用,立马忘的一干二净,原因 ...
- django权限管理(Permission)
什么是权限管理 权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自 己被授权的资源 权限管理好比如钥匙,有了钥匙就能把门打开,但是权限设置是有级别之分的,假如这个 系统有多 ...