【Java】简述DelayQueue的原理与使用

什么是DelayQueue?

        根据官方文档描述,DelayQueue是一种存储含过期时间的元素的无界(队列无容量限制)阻塞(使用到了锁)队列;队列头部元素是已经过期并且是过期时间最久的元素(在peek方法实现中,队头元素不一定符合这一定义,队头元素也有可能是未过期,但是队列中离过期时间最近的元素),如果队列中没有元素过期,则队列头部没有元素(对于poll方法而言),使用poll方法时会返回null;除此之外,这些没有过期的元素会当作正常元素对待,使用size获取队列长度时,会返回所有过期、未过期的元素之和。

DelayQueue中存储的元素有何要求?

        (1)所有想要进入DelayQueue中的元素都必须实现一个接口:Delayed

        (2)Delay接口又继承自Comparable接口,这就意味着元素在实现getDelay方法的同时还要实现compareTo方法;getDelay方法的实现逻辑一般是返回元素剩余的过期时间负数x代表已经过期x时间、0代表当前正好过期、正数y代表再过y时间后过期

        (3)compareTo方法的实现逻辑一般是比较两个元素getDelay方法的返回值,根据比较结果,由优先级队列组织元素在队列中的先后顺序

DelayQueue字段分析

        (1)ReentrantLock:用于线程同步,锁的一种实现(支持可重入)。

        (2)PriorityQueue:优先级队列,DelayQueue底层使用该队列基于元素的剩余过期时间进行排序,规定元素的弹出次序。

        (3)leader:leader线程,应用leader/follower模式的思想。

        (4)Condition:线程等待时的“休息室”,与ReentrantLock配套使用。

//lock实现
private final transient ReentrantLock lock = new ReentrantLock();

//基于优先级队列控制元素的弹出次序
private final PriorityQueue<E> q = new PriorityQueue<E>();

//leader线程

private Thread leader = null;

//线程await时的“休息室”

private final Condition available = lock.newCondition();

DelayQueue关键方法分析

        (1)offer:添加元素到队列中(add、put底层都是直接调用该方法)。

public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        //首先获取锁
        lock.lock();
        try {
            //获取锁成功后将元素加入优先级队列中
            q.offer(e);
            //如果此时队列头部元素就是刚加入队列中的元素
            //1.说明此时队列中全是未过期的元素
            //2.可能有线程正在等待队头元素过期,将leader线程置为null
            //3.//唤醒在休息室等待的线程
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            //在finally块释放锁,防止死锁
            lock.unlock();
        }
    }

        (2)peek:取出队列头部元素。

public E peek() {
        final ReentrantLock lock = this.lock;
        //获取锁
        lock.lock();
        try {
            //队列可能存在三种情况:
            //1.所有元素均未到过期时间:此时返回离过期时间最近的元素
            //2.有一部分元素过期、一部分未过期:此时返回过期元素中,过期最久的元素
            //3.所以元素都过期了:此时返回已经过期的元素中,过期最久的元素
            return q.peek();
        } finally {
            //finally块释放锁,防止死锁
            lock.unlock();
        }
    }

        (3)poll:取出并移除队列头部元素。

public E poll() {
        final ReentrantLock lock = this.lock;
        //获取锁
        lock.lock();
        try {
            E first = q.peek();
            //1.队列中没有元素:first == null
            //2.队列中所有元素都还没过期:first.getDelay(NANOSECONDS) > 0
            //若上述两个现象有一个存在,直接返回null
            if (first == null || first.getDelay(NANOSECONDS) > 0)
                return null;
            else
                //若队列既不为空,也有元素以及过期,则直接返回队列头部元素
                return q.poll();
        } finally {
            //释放锁,防止死锁
            lock.unlock();
        }
    }

        (4)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)
                        //队列不为空,且队头元素已经过期。直接返回队头元素
                        return q.poll();
                    //队头元素还未过期,当前线程将进入休息室等待
                    //在等待时取消对队头元素的引用
                    first = null; // don't retain ref while waiting
                    if (leader != null)
                        //如果leader线程不为null
                        //说明此时已经有线程正在等待队头元素到期
                        available.await();
                    else {
                        //如果leader线程为空,说明此时没有线程等待队头元素到期
                        //1.设置当前线程为leader线程
                        //2.让当前线程到休息室中等待delay时间
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            //等待结束后,若当前线程还是leader线程,则将leader置为null
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            //leader为null并且队列不为null,那么唤醒正在休息室等待的线程
            if (leader == null && q.peek() != null)
                available.signal();
            //释放锁
            lock.unlock();
        }
    }

DelayQueue的简单使用

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class DelayQueueTest {
    static DelayQueue<Message<String>> queue = new DelayQueue<Message<String>>();
    public static void main(String[] args) {
        //添加三个过期时间不同的元素到队列中
        for (int i = 1; i <= 3; i++) {
            queue.add(new Message<String>(i,"任务" + i));
        }
        
        //创建一个线程,不断取出DelayQueue中的元素,直至队列为空
        new Thread(() ->{
            while(!queue.isEmpty()){
                Message<String> msg = queue.poll();
                System.out.println(msg);
            }
        }).start();
    }

    static class Message<E> implements Delayed {
        //延迟时间
        long delayTime;
        
        //过期时间:当前时间 + 延迟时间
        long expireTime;
        
        //真正的数据
        E data;

        public Message(long delayTime, E data) {
            this.delayTime = delayTime;
            this.data = data;
            this.expireTime = System.currentTimeMillis() + delayTime;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            long leftTime = unit.convert(this.expireTime - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
            return leftTime;
        }

        @Override
        public int compareTo(Delayed o) {
            return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
        }

        @Override
        public String toString() {
            return "Message{" +
                    "delayTime=" + delayTime +
                    ", expireTime=" + expireTime +
                    ", data=" + data +
                    '}';
        }
    }
}

        运行结果:

        三个任务可以认为是同时创建,由于任务1的延迟时间最短,它最先过期,所以最先被打印的是任务1,任务2、3的打印顺序也是相同的原理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值