【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 ...
随机推荐
- appendChild()方法遇到的问题
在使用appendChild()方法中遇到了一个问题: 下面的代码可以正常插入多个新元素 <input type="button" value="在后面插入新元素& ...
- undo空间满的处理方法(含undo的学习与相关解释)
1.查看数据库当前实例使用的是哪个UNDO表空间: show parameter undo_tablespace 2.查看UNDO表空间对应的数据文件和大小 pages col file_name f ...
- Dynamic Programming | Set 2 (Optimal Substructure Property)
正如我们在 Dynamic Programming | Set 1 (Overlapping Subproblems Property) 中讨论的那样,当一个问题具有以下2种性质时,建议使用动态规划来 ...
- django-celery 创建多个broker队列 异步执行任务时指定队列
一.这里不再详细述说 django 框架中如何使用celery, 重点放在如何实现创建多个队列, 并指定队列存放异步任务 笔者使用 django-celery==3.2.2 模块, 配置项及配置参 ...
- 五花八门的CSS
一.颜色 rgba(0, 0, 0, 0.5) rgba括号中前3个数字代表着 red green blue三种颜色的rgb值,0-255,最后一个是设定这个颜色的透明度即alpha值.范围从0到1, ...
- kubernetes集群搭建(2):docker私有仓库
kubernetes集群搭建(1):环境准备 中各节点已经安装好了docker,请确认docker已启动并正常运行 1.通过命令启动私库 docker run -d -p 5000:5000 --pr ...
- MySQL:索引
索引的目的在于提高查询效率,它的作用就相当于一本书的目录: 1. 常见的索引模型 1.1 哈希表 优点:适用于等值查询的场景: 缺点:范围查询效率较低: 1.2 有序数组 优点:范围查询和等值查询效率 ...
- Dispatch Queue 之 dispatch_async
 可以看到,在queue里的任务,不仅仅是一个函数,也可以是一个其他的queue. 下面是一个例子.  全局队列的处理 一般一个全局队列有多个线程,这些个线程会遍历并处理链表里的任务. 对于全局队 ...
- 音频科普---oggs
做为一个做音频的人,很多基础的东西还是要牢记的.最近一个客户用ogg格式的音频,感觉这个很陌生,就翻了这方面的 资料.好比是认识一个大牛,只有在你有一个困扰你很久的困难问题被他瞬间解决的时候,才知道什 ...
- LeetCode--No.011 Container With Most Water
11. Container With Most Water Total Accepted: 86363 Total Submissions: 244589 Difficulty: Medium Giv ...