DelayQueue源码分析

161 阅读4分钟

简介

DelayQueue是一个支持延迟获取元素的无界阻塞队列,队列内部组合PriorityQueue优先级队列来实现元素的超时获取,队列中的元素必须实现Delayed接口,在创建元素的时候,指定多久之后获取元素,只有在延时期满的时候才能够获取。

DelayQueue使用Leader/Follower多线程网络模型,每次只能有一个leader,多个follower

对PriorityQueue优先级队列不太明白的可以看PriorityQueue源码分析

源码分析

主要属性

 private final transient ReentrantLock lock = new ReentrantLock(); // 排它锁
 
 private final PriorityQueue<E> q = new PriorityQueue<E>(); // 线程非安全的优先级队列
 
 private Thread leader = null; // leader 线程
 
 private final Condition available = lock.newCondition(); // 等待队列

主要方法

入队

public boolean offer(E e) {
    final ReentrantLock lock = this.lock; // 获取独占锁
    lock.lock(); // 加锁
    try {
        q.offer(e); // 加入优先级队列
        if (q.peek() == e) { // 如果当前加入的元素在堆顶
            leader = null; // leader = null 这样可以重新选择新的leader
            available.signal(); // 唤醒一个等待的线程
        }
        return true;
    } finally {
        lock.unlock();
    }
}

当前添加的元素如果是堆顶元素,就唤醒一个等待的线程。
这里有两种情况:

  1. 堆中不存在元素,如果存在先get后put情况,这里就通知等待的线程,堆中存在元素了
  2. 堆中有其它元素,当前添加的元素是堆顶元素,也唤醒一个等待的线程,其实这里的唤醒一般就是重新选举leader

出队

public E poll() {
    final ReentrantLock lock = this.lock; // 获取独占锁
    lock.lock(); // 加锁
    try {
        E first = q.peek(); // 获取堆顶元素
        if (first == null || first.getDelay(NANOSECONDS) > 0) // 如果没有元素或者堆顶元素还没有过有效期,直接返回null
            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) // 超时获取元素,已经超时,直接返回null
                    return null;
                else
                    nanos = available.awaitNanos(nanos); // 堆中存在元素且还没过有效期,挂起剩余的时间
            } else { // 堆中存在元素
                long delay = first.getDelay(NANOSECONDS); // 堆顶元素剩余的时间
                if (delay <= 0) // 堆顶元素时间已到
                    return q.poll(); // 返回堆顶元素
                if (nanos <= 0) // 堆顶元素还没过有效期,但当前线程已经超时,直接返回null
                    return null;
                /**
                 * 说明下这里first=null的目的:
                 * a线程过来,检查到堆顶元素没有超时,在下面被挂起了
                 * b线程从await中苏醒,拿走了堆顶元素
                 * 接下来,如果发生gc,被拿走的元素是要被gc的,如果在这里first还指向那个元素,是gc不了的
                 */
                first = null; // don't retain ref while waiting
                if (nanos < delay || leader != null) //超时等待时间小于 堆顶元素超时时间或者已经存在leader
                    nanos = available.awaitNanos(nanos); // 挂起超时等待的时间
                else {   // 超时等待时间 > 延迟时间 并且没有其它线程在等待,那么当前元素成为leader,表示leader 线程最早 正在等待获取元素
                    Thread thisThread = Thread.currentThread(); // 拿到当前线程
                    leader = thisThread; // leader 指向当前线程
                    try {
                        long timeLeft = available.awaitNanos(delay); // 挂起堆顶元素剩余(多久超时)的时间,并返回剩余等待的时间
                        /*
                         delay: 堆顶元素的过期时间
                         timeLeft: awaitNanos 剩余的时间
                         nanos -= delay - timeLeft; 剩余的超时时间
                         */
                        nanos -= delay - timeLeft;
                    } finally {
                        if (leader == thisThread) // 如果leader还指向当前线程,说明堆顶元素没有变化
                            leader = null; // 清空leader,让其它线程有机会成为leader
                    }
                }
            }
        }
    } finally {
        if (leader == null && q.peek() != null) // 如果leader是null且队列中有元素
             /*
                 唤醒一个等待的线程,让它有机会成为leader,但唤醒的这个线程不一定能够成为leader,
                  如果在当前线程释放锁之后,此时有新的线程过来,新的线程且拿到了锁,新的线程可能会成为leader
                  leader 只能有一个
             */
            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); // 堆顶元素还有多久超时
                if (delay <= 0) // 如果已经超时
                    return q.poll(); // 返回堆顶元素
                /**
                 * 说明下这里first=null的目的:
                 * a线程过来,检查到堆顶元素没有超时,在下面被挂起了
                 * b线程从await中苏醒,拿走了堆顶元素
                 * 接下来,如果发生gc,被拿走的元素是要被gc的,如果在这里first还指向那个元素,是gc不了的
                 */
                first = null;  // don't retain ref while waiting
                if (leader != null) // 已经有leader了
                    available.await(); // 无期限挂起
                else { // 还没有leader
                    Thread thisThread = Thread.currentThread(); // 拿到当前线程
                    leader = thisThread; // leader 指向当前线程
                    try {
                        available.awaitNanos(delay); // 挂起堆顶元素剩余(多久超时)的时间
                    } finally {
                        if (leader == thisThread) // 如果leader还指向当前线程,说明堆顶元素没有变化
                            leader = null; // 清空leader,让其它线程有机会成为leader
                    }
                }
            }
        }
    } finally {
        if (leader == null && q.peek() != null) // 如果leader是null且队列中有元素
            /*
                 唤醒一个等待的线程,让它有机会成为leader,但唤醒的这个线程不一定能够成为leader,
                  如果在当前线程释放锁之后,此时有新的线程过来,新的线程且拿到了锁,新的线程可能会成为leader
                  leader 只能有一个
             */
            available.signal();
        lock.unlock(); // 释放锁
    }
}

Condition 条件在阻塞时会释放锁,在被唤醒时会再次获取锁,获取成功才会返回。 当进行超时等待时,阻塞在Condition 上后会释放锁,一旦释放了锁,那么其它线程就有可能参与竞争,某一个线程就可能会成为leader,leader是用来减少不必要的竞争,如果leader不为空说明已经有线程在取了,设置当前线程等待即可。(leader 就是一个信号,告诉其它线程:你们不要再去获取元素了,它们延迟时间还没到期,我都还没有取到数据呢,你们要取数据,等我取了再说)