DelayQueue 源码解析

DelayQueue简介

DelayQueue 是 Java 中提供的一种特殊类型的无界阻塞队列(blocking queue),它属于 java.util.concurrent 包。DelayQueue 中的元素只能在其延迟期满后才能被取走。这种队列非常适合处理定时任务调度、任务超时处理等需要延迟执行的场景。
在这里插入图片描述
DelayQueue中存放的元素必须实现Delayed接口,并且需要重写getDelay方法(计算是否到期)。

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

DelayQueue源码解析

类定义

DelayQueue继承了AbstractQueue类,实现了BlockingQueue接口。

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

DelayQueue 核心成员变量

//可重入锁,实现线程安全的关键
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();
  • lock:DelayQueue存取是线程安全的,DelayQueue就是基于ReentrantLock独占锁确保存取操作的线程安全
  • q: 延迟队列要求元素按照到期时间进行升序排列,所以添加元素时势必需要进行优先级排序。所以DelayQueue底层元素的存取都是通过这个优先队列PriorityQueue的成员变量q来管理的。
  • leader:在 DelayQueue 中,多个线程可能会同时等待队列中的元素变得可用。因为 DelayQueue 中的元素只能在其延迟期满后才能被取走,所以需要一种机制来高效地管理这些等待线程。leader 的作用就是帮助实现这一点。当多个线程调用 take 或 poll 并且队列中没有到期的元素时,这些线程会进入等待状态,其中一个线程会被选为 leader,这个 leader 线程将负责等待下一个到期元素的时间
  • available:leader线程等待唤醒操作的交互是通过available实现的,假如线程1尝试在空的DelayQueue获取任务时,available就会将其放入等待队列中。直到有一个线程添加一个延迟任务后通过available的signal方法将其唤醒。

构造方法

  • 默认构造方法
public DelayQueue() {}
  • 传入集合的构造方法
public DelayQueue(Collection<? extends E> c) {
    this.addAll(c);
}

常用方法

  • boolean add(E e):将元素插入队列。
  • boolean offer(E e):将元素插入队列(与 add 方法功能相同)。
  • E poll():获取并移除队列头部的元素,如果队列为空,则返回 null。
  • E peek():获取但不移除队列头部的元素,如果队列为空,则返回 null。
  • int size():返回队列中的元素数量。
  • boolean isEmpty():检查队列是否为空。
add方法

add方法调用offer方法实现元素入对

    public boolean add(E e) {
        return offer(e);
    }
offer方法
public boolean offer(E e) {
    //尝试获取lock
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //如果上锁成功,则调q的offer方法将元素存放到优先队列中
        q.offer(e);
        //调用peek方法看看当前队首元素是否就是本次入队的元素,如果是则说明当前这个元素是即将到期的任务(即优先级最高的元素)
        if (q.peek() == e) {
            //将leader设置为空,通知调用取元素方法而阻塞的线程来争抢这个任务
            leader = null;
            available.signal();
        }
        return true;
    } finally {
        //上述步骤执行完成,释放lock
        lock.unlock();
    }
}
  1. 获取ReentrantLock
  2. 调用PriorityQueue方法将元素入队
  3. 判断当前队列的队头元素是否为添加的元素,如果是说明当前元素优先级最高,将leader设置为null,通知因为队列为空时调用take方法导致阻塞的线程争抢元素。
  4. 释放lock。
peek方法

直接取出队头元素

    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.peek();
        } finally {
            lock.unlock();
        }
    }
poll方法
//指定获取元素的最长等待时间
  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)
                    //如果队头元素为空,且等待时间小于等于0,则返回空
                        return null;
                    else
                    //available等待nanos到期
                        nanos = available.awaitNanos(nanos);
                } else {
                //队头不为空,取出队头元素的到期时间
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                    //调用q.poll方法返回到期元素,并将该元素从队列中移除
                        return q.poll();
                    if (nanos <= 0)
                    //未到期,但到达等待时间返回null
                        return null;
                    first = null; // don't retain ref while waiting
                    if (nanos < delay || leader != null)
                    //如果等待时间小于超时时间或者leader不为空,说明正有线程作为leader并等待一个任务到期,则当前线程等待nanos时间
                        nanos = available.awaitNanos(nanos);
                    else {
                    //将leader设为当前线程
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                        //等待有限期时间
                            long timeLeft = available.awaitNanos(delay);
                            nanos -= delay - timeLeft;
                        } finally {
                         //等待任务到期时,释放leader引用,进入下一次循环将任务return出去
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
        //当leader为null,并且队列中有任务时,唤醒等待的获取元素的线程。
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }
take方法
public E take() throws InterruptedException {
// 获取锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
            //调用q.peek取出队头元素
                E first = q.peek();
                if (first == null)
                //队头元素为空,调用available.await();将线程阻塞
                    available.await();
                else {
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                    //判断当前队头元素是否到期,调用q.poll()返回当前元素,并将元素从队头移除。
                        return q.poll();
                    first = null; // don't retain ref while waiting
                    //否则,当leader不为空,进入等待状态
                    if (leader != null)
                        available.await();
                    else {
                    //当leader为空,把leader设置为当前线程
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                        //阻塞delay时间
                            available.awaitNanos(delay);
                        } finally {
                        //等待任务到期时,释放leader引用,进入下一次循环将任务return出去
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
         // 收尾逻辑:当leader为null,并且队列中有任务时,唤醒等待的获取元素的线程
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

DelayQueue常见问题

DelayQueue 的实现原理是什么?

DelayQueue底层使用优先队列PriorityQueue来存储元素,而PriorityQueue采用二叉小顶堆即优先级最高的任务放到堆顶,使得DelayQueue对于延迟任务优先级的管理十分方便。同时,DelayQueue为了保证线程安全还用到了ReentrantLock,确保单位时间内只有一个线程可以操作延迟队列。最后,为了实现多线程之间的等待和唤醒的交互效率,DelayQueue用到了Condition,通过Condition的await和signal方法完成多线程之间的等待唤醒(如果线程调用了take方法但是没有元素到期的话,会将当前线程阻塞)

DelayQueue使用场景有哪些?

DelayQueue通常用于定时任务调度和缓存过期删除。

DelayQueue中Delayed接口的作用是什么?

Delayed接口两个核心方法compareTo以及getDelay。不实现这两个方法,DelayQueue无法得知
当前任务的剩余时长以及任务优先级

DelayQueue 和 Timer/TimerTask 的区别是什么?

DelayQueue 和 Timer/TimerTask 都可以用于实现定时任务调度,但是它们的实现方式不同。DelayQueue 是基于优先级队列和堆排序算法实现的,可以实现多个任务按照时间先后顺序执行;而 Timer/TimerTask 是基于单线程实现的,只能按照任务的执行顺序依次执行,如果某个任务执行时间过长,会影响其他任务的执行。另外,DelayQueue 还支持动态添加和移除任务,而 Timer/TimerTask 只能在创建时指定任务。

  • 17
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值