DelayQueue使用及源码解析

1、定义

DelayQueue顾名思义,它是个无边界延迟队列,它的底层是基于PriorityBlockingQueue实现的。该队列中的元素都是按照过期时间顺序排序的,队列头部放的是即将过期的元素该队列中的元素必须实现Delayed接口,getDelay定义了剩余到期时间,compareTo方法定义了元素排序规则。该队列不允许存放null元素。延时队列实现了Iterator接口,但Iterator()遍历顺序不保证是元素的实际存放顺序。
● DelayQueue特点:
● DelayQueue队列是无边界
● 队列中按照过期时间排序
● 队列中的元素必须实现Delayed接口
● 不允许存放null元素
● poll方法获取元素时,立即返回,如果没有过期的元素则返回null
● take方法获取元素时,如果没有过期的元素则会进行阻塞
● peek方法获取元素时,立即返回,不会删除该元素,即使没有过期的元素也会获取到
● 使用Iterator可以立即返回该队列中的元素,但是不保证顺序

2、使用案例

public static void main(String[] args) throws InterruptedException {
    DelayQueue<DelayElement> delayQueue = new DelayQueue<>();
    System.out.println("========开始时====" + System.currentTimeMillis());
    delayQueue.add(new DelayElement<>("1",7000));
    delayQueue.add(new DelayElement<>("2",5000));
    delayQueue.add(new DelayElement<>("3", 6000));

    new Thread(() -> {
        try {
            while (!delayQueue.isEmpty()) {
                System.out.println("队列中剩余元素数量:" + delayQueue.size() + "获取元素前的时间:" + System.currentTimeMillis());
                DelayElement take = delayQueue.take();
                System.out.println("获取到了:" + take.getData() + "此时时间:" + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }, "thread1").start();

    Thread.sleep(100);
    System.out.println("重新往队列中添加元素的时间:" + System.currentTimeMillis());
    delayQueue.add(new DelayElement<>("4", 100));
}

static class DelayElement<E> implements Delayed {
    private final long expireTime;
    private final E e;

    public DelayElement(E e,long delay) {
        this.expireTime = System.currentTimeMillis()+delay;
        this.e = e;
    }
    @Override
    public long getDelay(TimeUnit unit) {
        // 获取剩余延迟时间
        return unit.convert(expireTime-System.currentTimeMillis(),TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        //排序规则:按照过期时间排序
        DelayElement that = (DelayElement) o;
        if (this.expireTime < that.expireTime) {
            return -1;
        } else if (this.expireTime > that.expireTime) {
            return 1;
        } else {
            return 0;
        }
    }

    public E getData() {
        return e;
    }
}
输出:
========开始时====1713770593062
队列中剩余元素数量:3获取元素前的时间:1713770593101
重新往队列中添加元素的时间:1713770593200
获取到了:4此时时间:1713770593305
队列中剩余元素数量:3获取元素前的时间:1713770593305
获取到了:2此时时间:1713770598074
队列中剩余元素数量:2获取元素前的时间:1713770598074
获取到了:3此时时间:1713770599075
队列中剩余元素数量:1获取元素前的时间:1713770599075
获取到了:1此时时间:1713770600076

3、源码解析

3.1 成员变量

// 用于同步的锁
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();

3.2 入队操作

DelayQueue的入队方法有四个:put(E e)、offer(E e)、add(E e)、offer(E e, long timeout, TimeUnit unit),但由于DelayQueue是无界队列,所以入队的操作永远都不会堵塞,这四个方法其实调用的都是offer(E e)方法。

public boolean offer(E e) {
    // 入队加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 将元素插入到优先级队列中
        q.offer(e);
        // 如果新插入的元素在队列首部,说明新入队的元素比原队列中所有元素优先级都要高
        if (q.peek() == e) {
            // 将leader置为空 并唤醒等待队列中的其中一个线程
            leader = null;
            available.signal();
        }
        return true;
    } finally {
        lock.unlock();
    }
}

3.3 出队操作

DelayQueue的出队方法有两个:take方法、poll方法

// 检索并删除此队列的头部,如果此队列没有延迟过期的元素,则返回 null 
// 注意:该方法不会堵塞
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);
                // 等待时间小于0 说明已经过了预期执行的时间点 直接返回元素
                if (delay <= 0)
                    return q.poll();
                // 等待时间不为0 则继续
                first = null; // don't retain ref while waiting
                if (leader != null)
                    // 如果此时已经有线程在等待,则后续的线程直接进入等待
                    available.await();
                else {
                    // 获取当前线程
                    Thread thisThread = Thread.currentThread();、
                    // 将当前线程设置为leader 提醒后面进来的线程
                    leader = thisThread;
                    try {
                        // 进入等待 等待的时间为队列中元素的最短延迟时间
                        available.awaitNanos(delay);
                    } finally {
                        // 等待结束后 如果leader为自己 则释放leader
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        // 成功获取到元素后,如果队列不为空 并且没有线程在等待 则唤醒等待队列中的线程继续获取元素
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

入队:

  1. 加锁
  2. 元素入队
  3. 判断元素是否为延迟时间最短的元素
  4. 是,则唤醒等待队列中的线程,重新尝试出队操作

出队:

  1. 加锁
  2. 判断队首是否存在数据 不存在则直接返回null
  3. 判断队首元素的延迟时间delay是否小于0,小于则直接返回队首元素,并移除队列的队首元素
  4. 若delay大于0,判断是否已经有线程在等待,是则直接进行等待
  5. 反之,则当前线程等待delay时间
  6. delay时间之后,当前线程自动唤醒,继续尝试获取锁并重复以上流程
  7. 队首元素成功出队后,唤醒下一个等待的线程
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值