JUC - 20 DelayQueue

DelayQueue有序存储Delayed类型或者子类型的对象,没当从队列中取走元素时,需要等待延迟耗完才会返回该对象。

所谓Delayed类型,因为需要比较,所以继承了Comparable接口:

public interface Delayed extends Comparable {
    long getDelay(TimeUnit unit);
}

其实Delayed对象的排序和延迟长短是无关的,因为Comparable的compare方法是用户自己实现的,DelayQueue只是保证返回对象的延迟已经耗尽。

DelayQueue需要排序存储Delayed类型的对象同时具备阻塞功能,但是阻塞的过程伴有延迟等待类型的阻塞,因此不能直接使用BlockingPriorityQueue来实现,而是用非阻塞的版本的PriorityQueue来实现排序存储。

private final PriorityQueue q = new PriorityQueue();

因此DelayQueue需要自己实现阻塞的功能(需要一个Condition):

private final Condition available = lock.newCondition();

老规矩还是先来看offer方法:

public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e);
            // 如果原来队列为空,重置leader线程,通知available条件
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

顺便提一下,因为DelayQueue不限制长度,因此添加元素的时候不会因为队列已满产生阻塞,因此带有超时的offer方法的超时设置是不起作用的:

public boolean offer(E e, long timeout, TimeUnit unit) {
        // 和不带timeout的offer方法一样
        return offer(e);
    }

因为DelayQueue需要自己实现阻塞,因此关注的重点应该是两个带有阻塞的方法:没有超时的take方法和带有超时的poll方法。

普通poll方法很简单,如果延迟时间没有耗尽的话,直接返回null就可以了。

public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E first = q.peek();
            if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0)
                return null;
            else
                return q.poll();
        } finally {
            lock.unlock();
        }
    }

接下来看take和带timeout的poll方法,在看过DelayedWorkQueue之后这部分还是比较好理解的:

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
                // 如果队列为空,需要等待available条件被通知
                E first = q.peek();
                if (first == null)
                    available.await();
                else {
                    long delay = first.getDelay(TimeUnit.NANOSECONDS);
                    // 如果延迟时间已到,直接返回第一个元素
                    if (delay <= 0)
                        return q.poll();
                    // leader线程存在表示有其他线程在等待,那么当前线程肯定需要等待
                    else if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        // 如果没有leader线程,设置当前线程为leader线程
                        // 尝试等待直到延迟时间耗尽(可能提前返回,那么下次
                        // 循环会继续处理)
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            // 如果leader线程还是当前线程,重置它用于下一次循环。
                            // 等待available条件时,锁可能被其他线程占用从而导致
                            // leader线程被改变,所以要检查
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            // 如果没有其他线程在等待,并且队列不为空,通知available条件
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

再来看带有timeout的poll方法,和DelayedWorkQueue非常相似:

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
                        // 尝试等待available条件,记录剩余的时间
                        nanos = available.awaitNanos(nanos);
                } else {
                    long delay = first.getDelay(TimeUnit.NANOSECONDS);
                    if (delay <= 0)
                        return q.poll();
                    if (nanos <= 0)
                        return null;
                    // 当leader线程不为空时(此时delay>=nanos),等待的时间
                    // 似乎delay更合理,但是nanos也可以,因为排在当前线程前面的
                    // 其他线程返回时会唤醒available条件从而返回,
                    // 这里使用nanos和nonas

 

你可能感兴趣的:(JUC - 20 DelayQueue)