Java延时队列(DelayQueue)用起来真香啊啊啊~

Java延时队列(DelayQueue)的使用及内部源码解析

在这里插入图片描述

前言

Java 在JDK 1.5之后提供了一种实现延时任务的队列,取出的元素是队列中超出规定期限的元素,使用延时队列可以帮助解决以下场景出现的问题(不考虑最佳实现方案,这里主要讲的是DelayQueue的原理)

  1. 当用户点了个外卖下单之后,在30 min没有付款,自动取消订单
  2. 轮到你值班,在2小时内没有签到就会发短信、邮件提醒签到

与定时任务不同的是,延时任务是需要某种情况下才会触发的任务,如果没用户下单又或者下单后及时付款这个延时任务就不会执行

延时队列(DelayQueue)的使用方式

要明确一点,放在延时队列的元素,都需要实现一个Delayed接口,拿一个订单类来举例

首先我们创建一个订单类实现了Delayed接口,重写了getDelay()compareTo()

getDelay()主要用来判断存入队列的元素是否超时,超时则可以取出数据

compareTo()主要用来对队列中的元素进行排序

package com.hecl.zhenghe.DelayQueue;

import lombok.Data;
import org.apache.tomcat.jni.Time;

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

@Data
public class Order implements Delayed {
    private String user;
    private String order_id;
    private long order_time;
    public Order(String user, String order_id, long order_time, TimeUnit timeUnit){
        this.user = user;
        this.order_id = order_id;
        this.order_time = System.currentTimeMillis() + (order_time > 0 ? timeUnit.toMillis(order_time) : 0);
    }
    @Override
    public long getDelay(TimeUnit unit) {
        return order_time - System.currentTimeMillis();
    }

    @Override
    public int compareTo(Delayed o) {
        Order order = (Order) o;
        long to = this.order_time - order.getOrder_time();
        return to <= 0 ? -1 : 1;
    }

    @Override
    public String toString() {
        return "Order{" +
                "user='" + user + '\'' +
                ", order_id='" + order_id + '\'' +
                ", order_time=" + order_time +
                '}';
    }
}

写一个测试类,来模拟生成订单和订单超时做出的处理

package com.hecl.zhenghe.DelayQueue;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;

public class testDelayQueue {
    public static void main(String[] args) throws InterruptedException {
        Random random = new Random();
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        DelayQueue<Order> orders = new DelayQueue<>();
        System.out.println(df.format(new Date()) + "订单A加入队列");
        //生成订单A并加入队列
        Order order_A = new Order("ALiangX","testA_" +random.nextInt(100),10,TimeUnit.SECONDS);
        orders.offer(order_A);
        Thread.sleep(5000);
        //等待5s生成订单B并加入队列
        System.out.println(df.format(new Date()) + "订单B加入队列");
        Order order_B = new Order("Jack Ma","testB_" +random.nextInt(100),10,TimeUnit.SECONDS);
        orders.offer(order_B);
        Thread.sleep(5000);
        //再等待5s生成订单C并加入队列
        System.out.println(df.format(new Date()) + "订单C加入队列");
        Order order_C = new Order("Pony","testC_" +random.nextInt(100),10,TimeUnit.SECONDS);
        orders.offer(order_C);
        while(!orders.isEmpty()){
            //若没有超时的元素,take()会阻塞该线程
            Order order = orders.take();
            System.out.println("订单因为未支付:" +order.toString() + " 在 " + df.format(new Date()) + "被取出!!" );
        }
    }
}

我们来执行以下这段代码,以下是运行结果

2021-04-28 21:56:53订单A加入队列
2021-04-28 21:56:58订单B加入队列
2021-04-28 21:57:03订单C加入队列
订单因为未支付:Order{user='ALiangX', order_id='testA_85', order_time=1619618223270} 在 2021-04-28 21:57:03被取出!!
订单因为未支付:Order{user='Jack Ma', order_id='testB_23', order_time=1619618228276} 在 2021-04-28 21:57:08被取出!!
订单因为未支付:Order{user='Pony', order_id='testC_45', order_time=1619618233276} 在 2021-04-28 21:57:13被取出!!

上面代码的意思就是,当订单10s没有支付时,就会从该队列取出订单并取消,就实现了我们对订单超时未支付的处理了

DelayQueue源码解析

我们先来看看DelayQueue里的方法,这里我给大家展示的是我们经常使用到的方法

//看到类定义就知道,放入DelayQueue的对象都必须实现Delayed接口
//DelayQueue主要继承了AbstractQueue和实现了BlockingQueue接口
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E> {

    //定义了一个ReentrantLock独占锁来保证队列的线程安全
    private final transient ReentrantLock lock = new ReentrantLock();
    //定义了一个PriorityQueue,这里说一句,其实对象都是存储到这个队列里的
    //DelayQueue主要是对队列内对象的超时处理
    private final PriorityQueue<E> q = new PriorityQueue<E>();

    private Thread leader = null;

    //获取Condition对象
    private final Condition available = lock.newCondition();

    //DelayQueue<Order> orders = new DelayQueue<>();
    //通过这个构造方法我们就可以创建一个延时队列
    public DelayQueue() {}

    public DelayQueue(Collection<? extends E> c) {
        this.addAll(c);
    }

    //添加对象到队列中
    public boolean add(E e) {
        //具体还是调用了offer()
        return offer(e);
    }

    //将对象添加到队列中
    public boolean offer(E e) {
        //线程首先要获取锁才可以执行下面的方法
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //调用PriorityQueue的offer方法来将对象加入PriorityQueue队列中
            q.offer(e);
            if (q.peek() == e) {
                leader = null;
                //唤醒其它线程
                available.signal();
            }
            return true;
        } finally {
            //解锁
            lock.unlock();
        }
    }

    //具体还是调用了offer()来添加对象
    public void put(E e) {
        offer(e);
    }
	//经过一段时间才将对象加入队列
    public boolean offer(E e, long timeout, TimeUnit unit) {
        return offer(e);
    }

    //将队列中的第一个对象取出来,队列中就不含有这个对象了
    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
                //要不然直接执行PriorityQueue的poll方法将对象取出
                return q.poll();
        } finally {
            lock.unlock();
        }
    }

    //这个方法也是取出超时对象,与poll()的区别是当没有对象超时的时候,会阻塞当前线程,且唤醒其它线程
    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)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }
	//与poll()不同的是这里还传进来了两个参数long timeout, TimeUnit unit
    //表示当调用此方法获取超时对象时,若没有对象超时,则会阻塞线程一段时间
    //这个时间主要由传进来的这两个参数决定的,在超过这段时间还没对象超时就会直接返回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();
                if (first == null) {
                    if (nanos <= 0)
                        return null;
                    else
                        nanos = available.awaitNanos(nanos);
                } else {
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                        return q.poll();
                    if (nanos <= 0)
                        return null;
                    first = null; // don't retain ref while waiting
                    if (nanos < delay || leader != null)
                        nanos = available.awaitNanos(nanos);
                    else {
                        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();
        }
    }
	//获取队列中的第一个对象,不会移除这个对象
    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.peek();
        } finally {
            lock.unlock();
        }
    }

    //返回队列中的对象个数
    public int size() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.size();
        } finally {
            lock.unlock();
        }
    }

    //获取队列中的位于第一位的超时对象,若队列中没有对象或没有对象超时则返回null,反不然直接返回该对象
    private E peekExpired() {
        // assert lock.isHeldByCurrentThread();
        E first = q.peek();
        return (first == null || first.getDelay(NANOSECONDS) > 0) ?
            null : first;
    }

    //清空队列中存在的所有对象
    public void clear() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.clear();
        } finally {
            lock.unlock();
        }
    }

    //移除队列中的某个对象,成功返回true,失败返回false
    public boolean remove(Object o) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.remove(o);
        } finally {
            lock.unlock();
        }
    }
}

具体每个方法的作用我都标注在了每个方法的上边了,大家直接看代码就可

从上面的源码我们可以看出,其实储存在DelayQueue中的对象都被存储到了PriorityQueue里,大部分也是调用了PriorityQueue里的方法,DelayQueue只是多了一个对对象超时的处理和使用ReentrantLock来保证线程安全而已,想了解ReentrantLock的可以看看我这篇文章 [全网首发]多线程最全知识万字总结(源码解析 ps:不信你能一次看完,建议收藏)

我们下面重点来讲解这个PriorityQueue里的源码

PriorityQueue使用方式和内部源码解析

概念

PriorityQueue是在JDK 1.5 引入的一个非线程安全先进先出(FIFO——First In First Out)的队列,但是它与普通的FIFO队列不同的是,它可以根据存入对象的某种属性来对存入对象的优先级进行排序,优先级高的放在前面,优先执行

场景:

1.机场优先处理VIP客户的请求

2.操作系统优先处理优先级更高的程序

更通俗一点的说法就是有A、B、C三个线程,优先级是A最高,C最低,C先进入队列等待,这时B来了,由于B的优先级比C高,直接排在C前面了,这时A来了,A比B、C两个优先级都要高,一下就排在了B、C前边,这时队列内的顺序就变成了ABC

放入PriorityQueue里的对象都需要用Comparator比较器来对存入对象进行排序,这个排序由开发者自定

PriorityQueue的使用方式

放入PriorityQueue的对象不需要实现什么接口

首先我们定义一个Customer类,属性主要包括Name、Level这两个

package com.hecl.zhenghe.DelayQueue;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Customer {
    private String name;
    private int level;
}

写一个测试类来看看

package com.hecl.zhenghe.DelayQueue;

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;

public class testPriorityQueue {
    public static void main(String[] args) throws InterruptedException {
        //创建一个优先队列,并创建一个比较器重写compare方法
        Queue<Customer> PriorityQueues = new PriorityQueue(10, new Comparator<Customer>() {
            @Override
            public int compare(Customer o1, Customer o2) {
                return (o1.getLevel() - o2.getLevel());
            }
        });
        Random random = new Random();
        //对象入队,入队是随机的,每添加进一个元素,队列都会根据比较器来进行排序
        for(int i = 0;i<10;i++){
            int level = random.nextInt(50);
            Customer customer = new Customer("zhangsan_" + i,level);
            PriorityQueues.add(customer);
            System.out.println("入队 " + customer);
        }
        Thread.sleep(2000);
        System.out.println();
        //对象出队
        while (true){
            Customer customer = PriorityQueues.poll();
            if(customer == null) break;
            System.out.println("出队 " + customer);
        }
    }
}

执行结果

入队 Customer(name=zhangsan_0, level=43)
入队 Customer(name=zhangsan_1, level=26)
入队 Customer(name=zhangsan_2, level=37)
入队 Customer(name=zhangsan_3, level=6)
入队 Customer(name=zhangsan_4, level=49)
入队 Customer(name=zhangsan_5, level=26)
入队 Customer(name=zhangsan_6, level=16)
入队 Customer(name=zhangsan_7, level=5)
入队 Customer(name=zhangsan_8, level=5)
入队 Customer(name=zhangsan_9, level=31)

出队 Customer(name=zhangsan_7, level=5)
出队 Customer(name=zhangsan_8, level=5)
出队 Customer(name=zhangsan_3, level=6)
出队 Customer(name=zhangsan_6, level=16)
出队 Customer(name=zhangsan_1, level=26)
出队 Customer(name=zhangsan_5, level=26)
出队 Customer(name=zhangsan_9, level=31)
出队 Customer(name=zhangsan_2, level=37)
出队 Customer(name=zhangsan_0, level=43)
出队 Customer(name=zhangsan_4, level=49)

从上面的执行结果我们可以看出来,入队的过程是随机的,但是出队的时候,是根据level来进行排序从小到大进行输出,通过这个优先队列我们就可以先执行优先级别高的任务,接下来我们来看看PriorityQueue内部源码

PriorityQueue源码解析

在这里我们只讲述比较重要的几个方法和几个重要属性

构造器

//默认队列大小
private static final int DEFAULT_INITIAL_CAPACITY = 11;

//存储对象的数组
transient Object[] queue;

//队列中元素的个数
private int size = 0;

//比较器,用于对象的排序
private final Comparator<? super E> comparator;

//对队列操作的次数
transient int modCount = 0;

	//默认创建大小为11的队列,且不传入比较器,一般用于存储Integer等基本类型就无需指定比较器
	public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }
    //指定队列大小
    public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }
    
	//创建默认大小为11的队列,并传入比较器来对对象进行比较排序
    public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, comparator);
    }
    
	//上面的那几种构造器都是调用的这个构造方法来创建队列
    public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        // Note: This restriction of at least one is not actually needed,
        // but continues for 1.5 compatibility
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

队列内部实现原理(二叉树小顶堆/大顶堆)

小顶堆

除叶子节点,所有非叶子节点都不大于其左右节点的权值

大顶堆

除叶子节点,所有非叶子节点都不小于其左右节点的权值

那到底是大顶堆还是小顶堆呢?

主要还是通过我们传入的比较器来决定的,这里我们就用小顶堆来给大家举例

offer(E e)

此方法主要是添加元素时使用的方法,每行代码的意思我都标在下面了

还有一个add(E e),其源码如下

	public boolean add(E e) {
        return offer(e);
    }

其主要还是调用了offer方法,所以这里我们主要研究offer(E e)

public boolean offer(E e) {
    	//如果添加的对象为空时,则抛出空指针异常
        if (e == null)
            throw new NullPointerException();
    	//对队列操作次数+1
        modCount++;
    	//获取队列内元素个数
        int i = size;
    	//若队列对象个数比队列长度要长,则调用grow()创建一个新的队列,将原有的元素复制到新队列
    	//queue对象指针指向新的队列
        if (i >= queue.length)
            grow(i + 1);
    	//队列元素个数+1
        size = i + 1;
        if (i == 0) 
            queue[0] = e;
        else
            //这个方法是调整队列内元素位置的一个重要的方法
            siftUp(i, e);
        return true;
    }

	/**
     * 下面的两个方法是扩展队列的两个方法
     */
	private void grow(int minCapacity) {
        //获取原先队列的长度
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50%
        //若原先长度<64,则原先长度+2,否则原先长度*2得到新长度
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                         (oldCapacity + 2) :
                                         (oldCapacity >> 1));
        // overflow-conscious code
        //private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        //若超过了最大长度则调用hugeCapacity来进行处理
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        queue = Arrays.copyOf(queue, newCapacity);
    }
	private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
排序源码解析

因为底层为二叉树小顶堆的实现,所以每当有一个新的元素加入进来都会调整树的结构,下面就是进行树结构调整的源码

/**
     * 下面的三个方法是对插入对象进行排序
     * 参数k表示需要排序的对象的位置,初始值一般为所有对象的最后一位
     * 参数x表示需要排序的对象
     */
	private void siftUp(int k, E x) {
        //若传进来的比较器不为空则调用第一个方法、否则调用第二个方法
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }
	private void siftUpComparable(int k, E x) {
        //获取排序该对象的父类比较器,一般适用于基本类型
        Comparable<? super E> key = (Comparable<? super E>) x;
        //若k的位置,也就是对象的初始位置>0才进行排序,k == 0就不需要排序了,队列中只有一个元素排什么序
        //普及个小知识(二叉树父子节点的下标关系)
        //parentNo = (sonNo-1)/2
        //leftNo = parentNo*2+1
        //rightNo = parentNo*2+2
        //也就是知道了其中任意一个节点,都可以推算出其父子节点的位置
        while (k > 0) {
            //这个(k - 1) >>> 1 就相当于 (k - 1) / 2 
            int parent = (k - 1) >>> 1;
            //得到父节点对象
            Object e = queue[parent];
            //若key所持有的对象与e比较,比e大则直接跳出循环,
            //比e小则二者对调,直到key持有的对象比其父对象小为止
            //这里所使用的比较器就相当于一个对象调用了它的compareTo来与另一个对象比较
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }
	private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            //这个(k - 1) >>> 1 就相当于 (k - 1) / 2
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            //这里使用的是我们传进来的比较器来对两个对象进行比较
            if (comparator.compare(x, (E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }

我上一幅图给大家感受一下
在这里插入图片描述

图一是队列中预先存在的元素,接下来通过offer来加入一个新的元素4会怎么样
在这里插入图片描述
这时4排在最后一个,不符合我们小顶堆的性质,所以我们要调整一下站位,通过int parent = (k - 1) >>> 1;来获取父节点的位置,与父结点相比较,若比父节点小则与父节点进行交换
在这里插入图片描述
直到它比父节点大或者它的下标为0才会停止循环,通过父子节点的交换我们就维护了小顶堆的性质,把最小的数放在了根节点了

poll()

直接上源码解析

public E poll() {
    //如果队列内对象数量为0直接返回null
    if (size == 0)
        return null;
    //获取最后一个对象
    int s = --size;
    //对队列操作次数+1
    modCount++;
    //获取队首对象
    E result = (E) queue[0];
    //获取队尾对象
    E x = (E) queue[s];
    将队尾对象置空
    queue[s] = null;
    //若对尾对象不为空时,则调用siftDown来对剩余的对象进行排序
    if (s != 0)
        siftDown(0, x);
    return result;
}
排序源码解析
    private void siftDown(int k, E x) {
        //若比较器不为空则调用传进来的比较器进行排序,为空则调用默认的比较器
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }
	private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        //普及个小知识(二叉树父子节点的下标关系)
        //parentNo = (sonNo-1)/2
        //leftNo = parentNo*2+1
        //rightNo = parentNo*2+2
        //size >>> 1 相当于 size / 2
        int half = size >>> 1;      // loop while a non-leaf
        //当k也就是0,大于half也就是一半的size时才跳出循环
        while (k < half) {
            //获取左子树的下标
            int child = (k << 1) + 1; // assume left child is least
            //通过下标获取左子树对象
            Object c = queue[child];
            //获取右子树的下标
            int right = child + 1;
            //若右子树下标小于队列中存在的对象个数且左子树比右子树大时c就指向右子树
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            //若对象c比队尾元素x大时才进行交换,否则跳出循环
            if (key.compareTo((E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = key;
    }

	//下面的具体流程和上面差不多,只不过用到的比较器是我们传进去的,这里就不再说明了
    @SuppressWarnings("unchecked")
    private void siftDownUsingComparator(int k, E x) {
        //size >>> 1 相当于 size / 2
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

上图来给大家解释一下
在这里插入图片描述
上面这幅图都向大家解释了一个元素poll以及内部树结构的过程,这里就不再解释了,大家认真看图!!
在这里插入图片描述

peek()

我们来看看源码

	public E peek() {
        return (size == 0) ? null : (E) queue[0];
    }

源码很简单,如果队列的元素不为空的话,就直接返回队首的元素,没什么好解释的

remove(Object o)

我们直接来看源码

	public boolean remove(Object o) {
        int i = indexOf(o);
        if (i == -1)
            return false;
        else {
            removeAt(i);
            return true;
        }
    }
	private int indexOf(Object o) {
        if (o != null) {
            for (int i = 0; i < size; i++)
                if (o.equals(queue[i]))
                    return i;
        }
        return -1;
    }
    private E removeAt(int i) {
        // assert i >= 0 && i < size;
        modCount++;
        int s = --size;
        if (s == i) // removed last element
            queue[i] = null;
        else {
            E moved = (E) queue[s];
            queue[s] = null;
            siftDown(i, moved);
            if (queue[i] == moved) {
                siftUp(i, moved);
                if (queue[i] != moved)
                    return moved;
            }
        }
        return null;
    }

从源码我们可以看出要移除一个对象时,需要通过indexOf来定位到元素,然后然后调用removeAt来移除元素,移除完元素后再使用siftDownsiftUp来调整树结构,这里就不再赘述了,想要了解的大家往上翻翻
在这里插入图片描述

一键三连,冲啊!!!

家人们不要吝啬你们的一键三连啊
在这里插入图片描述

创作不易,转载引用请告知本作者!!!为了生活加油

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Java中的DelayQueue是一个特殊的队列,它只允许在指定的延迟时间之后才能从队列中取出元素。可以使用DelayQueue来实现一些延迟任务的功能,例如任务调度、缓存过期等。 DelayQueue基于PriorityQueue实现,但是它的元素必须实现Delayed接口,Delayed接口中定义了一个getDelay()方法,返回元素的延迟时间。 当从DelayQueue中取出元素时,如果该元素的延迟时间还没有到达,则该元素会被重新加入队列中,直到延迟时间到达。 以下是一个简单的使用DelayQueue的例子: ```java import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class DelayQueueExample { public static void main(String[] args) throws InterruptedException { DelayQueue<DelayedElement> delayQueue = new DelayQueue<DelayedElement>(); delayQueue.add(new DelayedElement("element1", 2000)); delayQueue.add(new DelayedElement("element2", 5000)); delayQueue.add(new DelayedElement("element3", 1000)); while (!delayQueue.isEmpty()) { DelayedElement element = delayQueue.take(); System.out.println("Taking element: " + element); } } static class DelayedElement implements Delayed { private String name; private long delayTime; public DelayedElement(String name, long delayTime) { this.name = name; this.delayTime = System.currentTimeMillis() + delayTime; } @Override public long getDelay(TimeUnit unit) { long diff = delayTime - System.currentTimeMillis(); return unit.convert(diff, TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { if (this.delayTime < ((DelayedElement) o).delayTime) { return -1; } if (this.delayTime > ((DelayedElement) o).delayTime) { return 1; } return 0; } @Override public String toString() { return "DelayedElement{" + "name='" + name + '\'' + ", delayTime=" + delayTime + '}'; } } } ``` 在上面的例子中,我们创建了一个DelayQueue,并向其中添加了三个DelayedElement元素。每个元素都有一个延迟时间,分别为2秒、5秒和1秒。 在主线程中,我们不断地从DelayQueue中取出元素,直到队列为空。当元素的延迟时间还没有到达时,它会被重新加入队列中,直到延迟时间到达。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉淀顶峰相见的PET

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值