[java队列]——DelayQueue
- DelayQueue介绍
- DelayQueue内部实现
- 基本属性
- 构造方法
- 入队
- 出队
DelayQueue介绍
前面介绍了LinkedBlockingQueue和ArrayBlockingQueue等阻塞队列,以及PriorityQueue优先级队列,这里讲介绍另一个队列DelayQueue,延迟队列。其特点如下:
- 延时阻塞
- 线程安全,通过重入锁控制
- 使用场景:定时任务
DelayQueue内部实现
基本属性
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>();
//当前正在排队等待取元素的线程
private Thread leader = null;
//队列是否有可用的元素可出队
private final Condition available = lock.newCondition();
结论:
- 底层使用优先级队列来实现延时
- 使用可重入锁的一个条件来控制并发线程安全。只用一个条件的原因是优先级队列是无界的,可以一直添加
- 可以看到延迟队列的元素必须实现Delayed接口,该接口也继承了Comparable接口用于优先级队列对元素大小的比较,其代码如下:
public interface Delayed extends Comparable<Delayed> {
//返回与此对象关联的剩余延迟,在给定时间单位。
long getDelay(TimeUnit unit);
}
构造方法
public DelayQueue() {}
public DelayQueue(Collection<? extends E> c) {
this.addAll(c);
}
结论:
- 构造方法非常简单,直接看代码吧
入队
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//调用优先级队列的入队方法
q.offer(e);
//获取队头元素如果为当前插入的元素
if (q.peek() == e) {
//如果添加的元素是堆顶元素,就把leader置为空,并唤醒等待在条件available上的线程
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
public void put(E e) {
offer(e);
}
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e);
}
结论:
- 入队代码非常简单,就是调用优先级队列的入队方法
- 由于DelayQueue是阻塞队列,因此它拥有前面博客介绍过的四个入队方法,由于底层使用优先级队列,因此其实其余的三个方法都只需调用offer方法。
出队
出队同样有四个方法remove,poll,poll(timeout),take
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();
}
}
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 = null; // don't retain ref while waiting
if (leader != null)
//若已有线程在等待出队,则阻塞等待
available.await();
else {
//若没有线程在等待出队,则将自己线程标识为正在等待出队的线程。
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
//等待delay时间后自动唤醒,此时仍有可能被其它线程出队了,这跟AQS有关,后面有机会会分享
available.awaitNanos(delay);
} finally {
//如果leader没有变化,则将leader设置为空。
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
//如果leader为空且,且队列元素不为空,则唤醒下一个等待线程
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
结论:
- poll方法获取不到队头元素或者队头元素还未到期,则直接返回null
- 而take方法则会阻塞等待,直到获取到元素为止才返回