DelayQueue源码刨析
前言
public class DelayQueue extends AbstractQueue
implements BlockingQueue {
AbstractQueue:Queue队列的基本骨架
BlockingQueue:阻塞队列接口
Delayed:延迟接口,含有getDelayed方法返回对象剩余延迟时间,该接口实现了Comparable
提示:以下是本篇文章正文内容,下面案例可供参考
一、DelayQueue源码部分
1.构造器
//无参空构造函数
public DelayQueue() {
}
//带参构造函数
public DelayQueue(Collection<? extends E> c) {
this.addAll(c);
}
//加入一个集合的元素到该队列中去
public boolean addAll(Collection<? extends E> c) {
if (c == null) //不能加入空元素
throw new NullPointerException(); //空指针异常
if (c == this)
throw new IllegalArgumentException(); //非法算术异常(不能自己加自己)
boolean modified = false;
for (E e : c) //遍历集合元素添加到队列中去
if (add(e)) //调用了本类的offer方法,成功时返回true
modified = true;
return modified; //只有队列被修改并且没有抛出异常的情况下将会返回true
}
2.成员变量
//该延迟队列底层是用的一个优先级队列来存储数据的
private final PriorityQueue<E> q = new PriorityQueue<E>();
//全局锁
private final transient ReentrantLock lock = new ReentrantLock();
//当队列头部有新元素可用或新线程可能需要成为leader时发出的条件。
private final Condition available = lock.newCondition();
///
//关于Condition中线程的挂起和唤醒的一些方法的使用
//Condition的创建
ReentrantLock lock = new ReentrantLock(); //首先需要一把锁
Condition condition = lock.newCondition(); //通过该锁实例对象创建Condition
//该方法阻塞当前线程直到被其他线程唤醒或者该线程被中断
condition.await();
//该方法阻塞当前线程直到被其他线程唤醒或者该线程被中断或者超过指定long时间(时间单位为纳秒)
condition.awaitNanos(long);
//唤醒阻塞线程中的一个
condition.signal();
//唤醒阻塞的所有线程
condition.signalAll();
//执行上面方法时必须先获得lock锁(lock.lock()),否则将抛出异常java.lang.IllegalMonitorStateException。
//当一个线程要从队列中取元素时,队首的元素可能剩余延迟时间>0此时,该线程需要
//等待剩余延迟时间然后才可以等待其他线程将其唤醒,此时其他线程也可从队列中获取元素,发现leader不为null的时候,其他线程将会直接阻塞等待唤醒
private Thread leader = null;
3.主要方法
入队
offer方法
public boolean offer(E e) {
final ReentrantLock lock = this.lock; //获取全局锁
lock.lock(); //加锁
try {
q.offer(e); //向优先级队列加入元素e
//如果加入的元素是队列中延迟时间最小的(优先级最高的)该元素则位于队首
if (q.peek() == e) {
leader = null; //leader线程所指向线程将于其他线程有相等的竞争
available.signal(); //唤醒其他等待出队操作的线程(随机唤醒一个)
}
return true; //入队成功返回true
} finally {
lock.unlock(); //释放锁
}
}
//该方法与上面方法相同
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e);
}
//该方法用于获得队首元素(加锁了)
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return q.peek();
} finally {
lock.unlock();
}
}
//与offer方法相同
public void put(E e) {
offer(e);
}
出队
public E poll() {
final ReentrantLock lock = this.lock; //全局锁
lock.lock(); //加锁
try {
E first = q.peek(); //获得优先级队列中的队首元素(不移除该元素)
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;//队列为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) {
//队首为null则队列为空
if (nanos <= 0) //等待时间<=0直接返回null
return null;
else //否则该线程阻塞nanos时间后自动被唤醒或中途被其他线程唤醒
nanos = available.awaitNanos(nanos);
} else {
//队列非空的情况
long delay = first.getDelay(NANOSECONDS); //获取队首元素剩余延迟时间
if (delay <= 0) //剩余延迟时间<= 0
return q.poll(); //直接出队poll过程 //a
if (nanos <= 0) //剩余延迟时间>0并且超时时间 <= 0的情况下(该线程不等待)
return null; //直接返回null //b
first = null; // don't retain ref while waiting
//1.这里当超时时间小于延迟时间时,该线程仍然等待nanos时间,因
//为此时他释放了自己锁持有的锁资源,其他线程可以进行入队操作,
//且队首元素可能被改变
//2.如果当超时时间大于等于延迟时间时,并且leader不为null(说明
//也有线程在进行出队操作),将会等待nanos时间
if (nanos < delay || leader != null)
nanos = available.awaitNanos(nanos);
else {
//3. 没有其他线程在进行入队操作时
Thread thisThread = Thread.currentThread(); //获取当前线程
leader = thisThread; //设置成员变量leader为当前线程
try {
long timeLeft = available.awaitNanos(delay); //等待指定延迟时间
//这里等待指定延迟时间后为什么不直接去得队首元素
//和上面原因类似,此时其他线程可能进行入队或出队操作改变了队首元素
nanos -= delay - timeLeft; //更新超时时间
} finally {
//1.这里leader可能为空因为在当前线程阻塞的时候,其他入队操作的线程可能将leader赋值为null,leader为null说明 当前线程是被其他入队操作线程唤醒的
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
//队列中还有元素,并且不存在leader线程了
if (leader == null && q.peek() != null)
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) //队列为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) //队首元素的剩余延迟时间大于0,有其他线程也在出队操作
available.await(); //直接阻塞当前线程
else {
//队首为未过期元素并且当前没有其他线程出队阻塞
Thread thisThread = Thread.currentThread();
leader = thisThread; //将当前线程设置为leader
try {
available.awaitNanos(delay); //阻塞队首延迟时间后被唤醒
} finally {
//leader可能为null,因为其他线程的入队操作导致的
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
//队列中还含有其他元素并且leader为null
if (leader == null && q.peek() != null)
available.signal(); //唤醒其他阻塞在出队操作上的线程
lock.unlock(); //锁释放
}
}
总结
- DelayQueue 是一个延迟队列,该队列中的元素必须实现Delayed接口。
- 该队列底层使用的是一个完全二叉堆的优先级无界队列
- DelayQueue 队列实现了BlockingQueue,可以对队列的阻塞操作(出队入队操作同时使用一把全局锁)
出队: take,队列为null时阻塞当前线程等待其他线程的唤醒,队列不为null时,判断队首元素是否已过期,过期直接返回,未过期根据leader是否为null,决定该线程是阻塞只当时间还是阻塞等待其他线程的唤醒。poll,无参时队列中有过期元素时直接返回,否则返回null。带参时,与take类似只不过加了一个超时时间(该线程能等多久),超过超时时间将直接返回null。
入队:offer,put(直接调用的offer),入队成功时直接放回true。 - 该阻塞队列包含一个成员变量leader Thread,该变量多个线程都进行take时无需每个线程都等待指定的时间后自动被唤醒,只有第一个take的线程进行这样的处理,后面的take线程都需要进行判定leader是否为空来决定该线程是直接await。(awaitNonas方法自旋判断效率低)。
- DelayQueue 有一个condition变量来处理线程间唤醒和阻塞,使用相关方法是必须先获得相关锁且线程在await或awaitNonas时或释放掉当前所有的锁资源,再次被唤醒时重新竞争获得。
以下时Delayed接口
public interface Delayed extends Comparable<Delayed> {
/**
* Returns the remaining delay associated with this object, in the
* given time unit.
*
* @param unit the time unit
* @return the remaining delay; zero or negative values indicate
* that the delay has already elapsed
*/
long getDelay(TimeUnit unit);
}