【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 ...
随机推荐
- DL_1_week1_概论
standard NN,CNN,RNN,custom hybrid NN architecture(Radar) 结构化数据是数据的数据库,相比结构化数据计算机更难理解非结构化数据,人生来很容易理解非 ...
- go的数据库操作mysql
go get github.com/go-sql-driver/mysql package main; import ( "database/sql" _ "github ...
- Word中带圈数字
写论文时常常要求输入带圈数字,先在Word中输入代码,选中代码后按Alt+X(然后再粘贴到Excel中) 符号 代码⓪ 24ea① 2460② 2461③ 2462④ 2463⑤ 2464⑥ 2465 ...
- 在jenkins中调用maven的变量
Maven内置变量说明: ${basedir} 项目根目录(即pom.xml文件所在目录) ${project.build.directory} 构建目录,缺省为target目录 ${project. ...
- 「SDOI2016」储能表(数位dp)
「SDOI2016」储能表(数位dp) 神仙数位 \(dp\) 系列 可能我做题做得少 \(QAQ\) \(f[i][0/1][0/1][0/1]\) 表示第 \(i\) 位 \(n\) 是否到达上界 ...
- WebRTC 学习之 WebRTC 简介
本文使用的WebRTC相关API都是基于Intel® Collaboration Suite for WebRTC的. 相关文档链接:https://software.intel.com/sites/ ...
- 面试时遇到的题目。正则,replace()
function Fn(str){ this.str = str; } Fn.prototype.format = function(){ var arg = arguments; var dd = ...
- 教你怎么看网站是用react搭建的
概述 SPA和react可以说是web开发的分水岭,我一直在寻找判断网站是普通网站还是SPA抑或是react的方法.今天突然找到一个判断网站是不是react搭建的简便方法.现在记录下来供以后开发时参考 ...
- ubuntu16.04 编译出错:fatal error: SDL/SDL.h: No such file or directory
在ubuntu 16.04编译神经网络代码时候,遇到了这样一种错误? fatal error: SDL/SDL.h: No such file or directory 原因是SDL库没有安装,根据你 ...
- Python - 使用Pyinstaller将Python代码生成可执行文件
1 - Pyinstaller简介 Home-page: http://www.pyinstaller.org PyInstaller是一个能够在多系统平台(Windows.*NIX.Mac OS)上 ...