[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方法则会阻塞等待,直到获取到元素为止才返回