JUC之延时队列DelayQueue

一、概述

DelayQueue队列是个延时队列,底层是优先级队列PriorityQueue,从延时队列中获取元素时,只有过期的元素才可以取到。


二、原理

存放在DelayQueue的元素需要实现Delayed接口,例如DelayTask实现了Delayed的接口,并实现下面的两个方法:

getDelay(TimeUnit unit):获取剩余时间

compareTo(Delayed o):设置优先级队列中的优先级

常用的方法

offer:向队列中存元素,当队列中没有元素时,直接加入到队列,如果队列中有元素,调用DelayTask中的compareTo方法,根据队列的优先级把优先级高放到队列的前面。

    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 调用PriorityQueue.offer方法,向队列中插入元素
            q.offer(e);
            // 调用PriorityQueue.peek方法,如果当前元素为队首元素表示优先级最高,将leader设为null,唤醒等待的线程
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

PriorityQueue.offer方法如下,如果队列为空,直接将元素放到队列中,如果队列不为空,调用siftUp方法,放入队列成功会返回true

    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);
        size = i + 1;
        if (i == 0)
            queue[0] = e;
        else
            siftUp(i, e);
        return true;
    }

siftUp(i,e)方法如下:

    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }

    @SuppressWarnings("unchecked")
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
           // 调用DelayTask.compareTo方法,如果优先级低,直接放到队列中,如果优先级高,替换位置
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }

take方法:从队列中取元素

 public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
                // 获取队首元素
                E first = q.peek();
                if (first == null)
                    // 取出的队首数据为空,阻塞,等待offer线程唤醒
                    available.await();
                else {
                    // 获取队首元素的剩余时间
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                        // 小于0说明已经过期,从队列中取出元素返回
                        return q.poll();
                    first = null; // don't retain ref while waiting
                    if (leader != null)
                    // 如果leader不为null,代表有其他线程进行了take操作,阻塞   
                    available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        // 将leader设成当前线程
                        leader = thisThread;
                        try {
                        // 等到时间到或被打断或收到通知                            
                        available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                // 如果leader还是当前线程,就把leader设为null
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            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();
            if (first == null || first.getDelay(NANOSECONDS) > 0)
                return null;
            else
                return q.poll();
        } finally {
            lock.unlock();
        }
    }

三、例子

实现Delayed接口的任务,即放到延时队列的元素

package juc;

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

public class DelayTask implements Delayed {

    /**
     * 延时时间
     */
    private final long delayTime;
    /**
     * 过期时间
     */
    private final long expireTime;
    /**
     * 存放的数据
     */
    private String data;

    public DelayTask(long delay, String data) {
        delayTime = delay;
        this.data = data;
        expireTime = System.currentTimeMillis() + delay;
    }

    /**
     * 剩余时间=到期时间-当前时间
     * 被compareTo方法和DelayQueue的take方法调用
     */
    @Override
    public long getDelay(TimeUnit unit) {
        // 把剩余时间转换到需要的单位
        return unit.convert(this.expireTime - System.currentTimeMillis() , TimeUnit.MILLISECONDS);
    }

    /**
     * 优先队列里面优先级规则
     * 被offer方法调用,该优先级比较是快到期的排在队列的前面
     * compareTo方法返回 大于或等于0 表示优先级低,直接放在队列
     * 如果小于0,表示优先级高,需要和父级或根替换位置
     * 获取父级或根的方法: int parent = (k - 1) >>> 1; Object e = queue[parent];
     *
     */
    @Override
    public int compareTo(Delayed o) {
        return (int) (this.getDelay(TimeUnit.MILLISECONDS) -o.getDelay(TimeUnit.MILLISECONDS));
    }

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

测试demo

package juc;

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

public class DelayQueueTest {

    public static void main(String[] args) throws InterruptedException {
        DelayTask task1 = new DelayTask(5,"aaa");
        DelayTask task2 = new DelayTask(15,"bbb");
        DelayQueue<Delayed> delayQueue = new DelayQueue<>();
        // 延时时间通过构造器设置,在offer方法中设置不起作用,因为timeout和TimeUnit被忽略了
        boolean flag1 = delayQueue.offer(task1);
        boolean flag2 = delayQueue.offer(task2);
        System.out.println("task1 放入队列的结果:"+flag1);
        System.out.println("task2 放入队列的结果:"+flag2);
        for (int i=0; i<2; i++) {
            Delayed detail = delayQueue.take();
            System.out.println("从队列中获取到的数据:"+detail);
        }
    }
}

四、使用场景

比如做系统发布后,做一些监控的任务,参数的巡检,可以使用延时队列。


猜你喜欢

转载自blog.csdn.net/dam454450872/article/details/80271186