小白系列----DelayQueue源码剖析

DelayQueue

介绍

继承关系如下:

image-20210511082025891

DelayQueue是一个支持获取元素的无界阻塞队列。队列使用PriorityQueue来实现,关于PriorityQueue的方法见另一篇文章
小白学PriorityQueue ,队列中的元素必须实现Delayed接口,实现getDelay方法,该方法返回指定的延迟时间,即该元素存活多久后可以从队列中提取元素

举个例子:

// 参考最下的链接
public class DelayQueueTest1 {

    private static DelayQueue delayQueue  = new DelayQueue();

    public static void main(String[] args) throws InterruptedException {
		// 往延时队列中添加多个任务
        new Thread(new Runnable() {
            @Override
            public void run() {
                delayQueue.offer(new MyDelayedTask("task4",4000));
                delayQueue.offer(new MyDelayedTask("task2",2000));
                delayQueue.offer(new MyDelayedTask("task1",1000));
                delayQueue.offer(new MyDelayedTask("task3",3000));
            }
        }).start();
		// 循环去队列取元素,只有当队列中的元素延迟时间结束后才能取出元素,否则将阻塞
        while (true) {
            Delayed take = delayQueue.take();
            System.out.println(take);
        }
    }
}

/**
 *  compareTo 方法必须提供与 getDelay 方法一致的排序
 */
class MyDelayedTask implements Delayed{

    private String name ;
    private long start = System.currentTimeMillis();
    private long time ;

    public MyDelayedTask(String name,long time) {
        this.name = name;
        this.time = time;
    }

    /**
     * 需要实现的接口,获得延迟时间   用过期时间-当前时间
     * @param unit
     * @return
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert((start+time) - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
    }

    /**
     * 用于延迟队列内部比较排序   当前时间的延迟时间 - 比较对象的延迟时间
     * 指定优先级队列中的排序方法,这里是按照等待时间从小到大排序,即先取出等待时间小的元素
     * @param o
     * @return
     */
    @Override
    public int compareTo(Delayed o) {
        MyDelayedTask o1 = (MyDelayedTask) o;
        return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
    }

    @Override
    public String toString() {
        return "MyDelayedTask{" +
                "name='" + name + '\'' +
                ", time=" + time +
                '}';
    }
}

调试结果如下:

image-20210512220907854

可以看出延迟时间最小的元素在第一个位置,满足小顶堆的排序规则

延迟队列在日常工作中也十分常见,比如:

  • 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,如果从DelayQueue获取到元素,说明该元素已经到期了
  • 定时任务调度:使用DelayQueue保存当天执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,比如TimerQueue就是使用DelayQueue实现。

下面对核心代码进行解读

基本属性
private final transient ReentrantLock lock = new ReentrantLock();
// 优先级队列, 使用堆排序,插入的每个元素放在这个队列中
private final PriorityQueue<E> q = new PriorityQueue<E>();

// 当某个线程成为leader后, 其他线程将无限等待,只有在当前线程延迟时间结束后,才将leader释放赋null
// 在take、poll(timeout, unit) 返回元素之前必须唤醒其他线程
private Thread leader = null;

// 线程没有获取到元素,那么就将线程放入该等待队列中
private final Condition available = lock.newCondition();
// 构造
public DelayQueue() {}
插入元素(offer)
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();
    }
}
获取元素(take)

当延迟时间最短的元素时间等待时间结束后才返回, 如果一直没有获取到元素,线程会一直阻塞,直到被中断

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)
                    // 弹出元素,先要执行finally中代码
                    return q.poll();
                first = null; // don't retain ref while waiting
                // 在当前线程进入之前已经有其他线程更改了leader
         		// 因为await方法会释放所有锁资源,因此会有其他线程进入该方法
                if (leader != null)
                    available.await();
                else {
                    // 线程首次调用take时会执行该处
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        // 阻塞当前元素的延迟时间
                        available.awaitNanos(delay);
                    } finally {
                        if (leader == thisThread)
                            // 赋null,让其他线程获取leader
                            leader = null;
                    }
                }
            }
        }
    } finally {
        // 没有线程持有leader 并且有其他线程在等待队列中, 那么唤醒等待队列中的其他等待线程
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}
弹出元素(poll)

如果元素的等待时间还没有到,那么直接返回null

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 从优先级队列中获取堆顶元素
        E first = q.peek();
        // 如果堆顶元素是null, 或则延迟时间还没有结束,那么返回null
        if (first == null || first.getDelay(NANOSECONDS) > 0)
            return null;
        else
            return q.poll();
    } finally {
        lock.unlock();
    }
}
指定时间弹出元素

指定等待时间,如果在指定时间内还没有获取到一个过期的元素,那么直接返回null

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();
            // PriorityQueue中没有元素
            if (first == null) {
                // 等待时间已经到,返回null
                if (nanos <= 0)
                    return null;
                else
                    // 等待指定时间
                    nanos = available.awaitNanos(nanos);
            } else { // 当first不为null时
                // 获取first所需的时间
                long delay = first.getDelay(NANOSECONDS);
                // 时间到了,直接返回
                if (delay <= 0)
                    return q.poll();
                
                if (nanos <= 0)
                    return null;
                first = null; // don't retain ref while waiting
                // 先等待naos 时间
                if (nanos < delay || leader != null)
                    nanos = available.awaitNanos(nanos);
                else {
                    // 第一次需要设置当前线程为leader
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        long timeLeft = available.awaitNanos(delay);
                        nanos -= delay - timeLeft;
                    } finally {
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

参考链接:

https://www.cnblogs.com/myseries/p/10944211.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值