继承体系
DelayQueue实现了BlockingQueue,是一个阻塞队列。除此之外还组合了Delayed接口,这个接口的目的强制要求入队的数据必须实现此接口。DelayQueue是一个支持延时获取元素的无界阻塞队列,里面的元素全部都是“可延期”的元素,列头的元素是最先“到期”的元素,如果队列里面没有元素到期,是不能从列头获取元素的,哪怕有元素也不行。也就是说只有在延迟期到时才能够从队列中取元素。Delayed接口定义如下:
public interface Delayed extends Comparable<Delayed> {
long getDelay(TimeUnit unit);
}
重要属性
// 用于控制并发的锁
private final transient ReentrantLock lock = new ReentrantLock();
// 优先级队列,DelayQueue底层使用的是优先队列进行存储
private final PriorityQueue<E> q = new PriorityQueue<E>();
// 用于标记当前是否有线程在排队(仅用于取元素时)
private Thread leader = null;
// 条件,用于表示现在是否有可取的元素
private final Condition available = lock.newCondition();
构造函数
public DelayQueue() {}
public DelayQueue(Collection<? extends E> c) {
this.addAll(c);
}
构造函数很简单,默认构造器或者接受外部集合作为当前集合元素。
重要方法
入队方法
入队有好几个方法,但是最终都在调用offer方法:
public boolean add(E e) {
return offer(e);
}
public void put(E e) {
offer(e);
}
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e);
}
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 使用PriorityQueue实例的offer方法
q.offer(e);
// 说明当前元素e的优先级最小也就即将过期的
// 这时候激活avaliable变量条件队列里面的线程,通知他们队列里面有元素了
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
可以看到都是委托给了内部的优先队列进行存储,围绕优先队列进行并发控制。
出队方法
出队有4个方法:
- poll() 方法
- poll(long timeout, TimeUnit unit) 方法
- take() 方法
- remove(Object o) 方法
// DelayQueue未覆盖此方法,使用AbstractQueue默认实现
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 先查看队首是否超时,非超时则直接返回null
E first = q.peek();
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
return q.poll();
} finally {
lock.unlock();
}
}
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 {
// 获取当前线程,标记leader为当前线程
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 等待队首时长
long timeLeft = available.awaitNanos(delay);
nanos -= delay - timeLeft;
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// 成功出队后,如果leader为空且堆顶还有元素,就唤醒下一个等待的线程
if (leader == null && q.peek() != null)
// signal()只是把等待的线程放到AQS的队列里面,并不是真正的唤醒
available.signal();
// 解锁,这才是真正的唤醒
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);
// 如果小于0说明已到期,直接调用poll()方法弹出堆顶元素
if (delay <= 0)
return q.poll();
// 如果delay大于0 ,则下面要阻塞了
// 将first置为空方便gc,因为有可能其它元素弹出了这个元素
// 这里还持有着引用不会被清理
first = null; // don't retain ref while waiting
// 如果前面有其它线程在等待,直接进入等待
if (leader != null)
available.await();
else {
// 如果leader为null,把当前线程赋值给它
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 等待delay时间后自动醒过来
available.awaitNanos(delay);
} finally {
// 如果leader还是当前线程就把它置为空,让其它线程有机会获取元素
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
// 成功出队后,如果leader为空且堆顶还有元素,就唤醒下一个等待的线程
if (leader == null && q.peek() != null)
// signal()只是把等待的线程放到AQS的队列里面,并不是真正的唤醒
available.signal();
// 解锁,这才是真正的唤醒
lock.unlock();
}
}
总结
DelayQueue底层使用优先队列来存储数据,出栈入栈是转交给优先队列来执行的,在DelayQueue内部强制要求元素实现Delay方法,用来控制时间排序。在控制并发方面,使用重入锁并陈胜对应的Condition对象来保障线程安全问题。除此之外,存在leader字段存储当前操作中的线程,优化了线程阻塞通知逻辑。