DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
DelayQueue非常有用,可以运用在以下两个应用场景:
DelayQueue只有两个很简单的构造方法:
public DelayQueue() {}
public DelayQueue(Collection extends E> c) {
this.addAll(c);
}
DelayQueue类定义为:
public class DelayQueue extends AbstractQueue implements BlockingQueue
该类同样继承了AbstractQueue抽象类并实现了BlockingQueue接口,这里不再叙述。
队列中的元素必须实现Delayed接口,那我们先来看一下Delayed接口,Delayed接口的定义很简单:
public interface Delayed extends Comparable {
long getDelay(TimeUnit unit);
}
只有一个getDelay(TimeUnit)方法,该方法返回与此对象相关的的剩余时间。同时我们看到Delayed接口继承自Comperable接口,所以实现Delayed接口的类还必须要定义一个compareTo方法,该方法提供与此接口的getDelay方法一致的排序。
DelayQueue中比较重要的字段如下:
private final transient ReentrantLock lock = new ReentrantLock();
private final PriorityQueue q = new PriorityQueue();
private Thread leader = null;
private final Condition available = lock.newCondition();
其实看到这里,我们应该已经能够了解DelayQueue的大致实现思路了:
以支持优先级的PriorityQueue无界队列作为一个容器,因为元素都必须实现Delayed接口,可以根据元素的过期时间来对元素进行排列,因此,先过期的元素会在队首,每次从队列里取出来都是最先要过期的元素。
我们来看一下add(E e)方法:
public boolean add(E e) {
return offer(e);
}
该方法通过调用offer(E e)来添加元素:
public boolean offer(E e) {
// 获取全局独占锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 向优先队列中插入元素
q.offer(e);
// 如果队首元素是刚插入的元素,则设置leader为null,并唤醒阻塞在available上的线程
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
// 释放全局独占锁
lock.unlock();
}
}
PriorityQueue的入队操作与PriorityBlockingQueue基本一致,这里不再叙述。
下面我们主要介绍一下leader变量:
leader是等待获取队列头元素的线程,应用主从式设计减少不必要的等待。如果leader不等于空,表示已经有线程在等待获取队列的头元素。所以,通过await()方法让出当前线程等待信号。如果leader等于空,则把当前线程设置为leader,当一个线程为leader,它会使用awaitNanos()方法让当前线程等待接收信号或等待delay时间。
出队我们来看一下会阻塞的take()方法:
public E take() throws InterruptedException {
// 获取全局独占锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
// 获取队首元素
E first = q.peek();
// 队首为空,则阻塞当前线程
if (first == null)
available.await();
else {
// 获取队首元素的超时时间
long delay = first.getDelay(NANOSECONDS);
// 已超时,直接出队
if (delay <= 0)
return q.poll();
// 释放first的引用,避免内存泄漏
first = null; // don't retain ref while waiting
// leader != null表明有其他线程在操作,阻塞当前线程
if (leader != null)
available.await();
else {
// leader指向当前线程
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 超时阻塞
available.awaitNanos(delay);
} finally {
// 释放leader
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// leader为null并且队列不为空,说明没有其他线程在等待,那就通知条件队列
if (leader == null && q.peek() != null)
available.signal();
// 释放全局独占锁
lock.unlock();
}
}
这里为什么如果不设置first = null,则会引起内存泄漏呢?线程A到达,列首元素没有到期,设置leader = 线程A,这是线程B来了因为leader != null,则会阻塞,线程C一样。假如线程阻塞完毕了,获取列首元素成功,出列。这个时候列首元素应该会被回收掉,但是问题是它还被线程B、线程C持有着,所以不会回收,这里只有两个线程,如果有线程D、线程E…呢?这样会无限期的不能回收,就会造成内存泄漏。
Java并发编程之ArrayBlockingQueue阻塞队列详解
Java并发编程之ReentrantLock详解
Java并发编程之Condition详解
Java并发编程之PriorityBlockingQueue阻塞队列详解
方腾飞:《Java并发编程的艺术》
【死磕Java并发】-----J.U.C之阻塞队列:DelayQueue