JAVA并发包(十四):DelayQueue

本文深入解析了DelayQueue的数据结构和工作原理,它是基于PriorityQueue实现的最小堆结构,用于实现延迟任务的调度。文章详细介绍了其入队列、出队列的操作流程,以及并发控制机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DelayQueue可以说是加了定时的PriorityBlockingQueue,它也是最小堆的结构,不过节点的取出必须要等到延迟的时间。

一、内部代码结构

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E> {

    private final transient ReentrantLock lock = new ReentrantLock();
    // 延迟队列是基于PriorityQueue保存数据的,PriorityQueue内部结构跟PriorityBlockingQueue基本一致,只不过它是是非线程安全,非阻塞的
    private final PriorityQueue<E> q = new PriorityQueue<E>();

    /** 第一个等待取出节点的线程,如果leader不为空,说明队列不为空并且第一个节点被标记等待取出,后面出队列方法会讲到 */
    private Thread leader = null;

    /** 出队列的等待队列 */
    private final Condition available = lock.newCondition();
}

二、入队列

	public void put(E e) {
        // 直接调用offer方法
        offer(e);
    }

	public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        // 首先加锁
        lock.lock();
        try {
        	
            q.offer(e);
            if (q.peek() == e) {
            	// 第一个节点等于刚刚入队列的节点,说明这个节点是最小的,
            	// 需要第一个出队列,所以唤醒等待的线程
                leader = null;
                // 唤醒等待出队列的线程
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }
    

三、出队列

	public E poll() {
        final ReentrantLock lock = this.lock;
        // 首先加锁
        lock.lock();
        try {
            E first = q.peek();
            // 第一个节点不为空,并且延迟时间小于等于0才会出队列
            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);
                    // 第一个节点不为空,并且延迟时间小于等于0,则出队列
                    if (delay <= 0)
                        return q.poll();
                    // 这里置为null是很有必要的,这样当first对应的节点被删除或者出队列后可以被回收掉
                    first = null; // don't retain ref while waiting
                    // leader不为空说明已经有线程在等待第一个节点了
                    if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                        	// 阻塞等待剩余时间
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
        	// leader为空并且第一个节点不为空,则唤醒其他等待出队列的线程
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

四、总结

  1. DelayQueue的数据结构是基于PriorityQueue实现的,也是最小堆的结构。它的延迟是通过每个节点都实现Delayed接口的getDelay(TimeUnit unit)方法实现的,即出队列的第一个节点要延迟规定的时间才会出队列成功。
  2. 内部出入队列,删除节点操作都是通过重入锁来做并发控制。
  3. 出队列用了leader线程去等待第一个节点延迟固定的时间。其他并发出队列的线程会做阻塞直到唤醒,而不是同样阻塞等待固定的时间,这样的设计可以减少线程调度,较少锁竞争的冲突。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值