DelayQueue

163 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情。 本文学习DelayQueue,译为延时队列,按延时时间有序排列。

简介

DelayQueue是一个延时的、同步的、有序的、无界的阻塞队列。实现了BlockingQueue接口,基于PriorityQueue实现,使用PriorityQueue存储元素。只能存放实现了Delayed接口的对象,其中的对象只能在其延时时间才能从队列中取走。因为此队列基于PriorityQueue实现所以不可存放Null。


Delayed接口

延期的意思,获取延期剩余时间(这个时间可自定义单位)

实现它必须重写getDelay(TimeUnit unit)public int compareTo(T o);方法。

public interface Delayed extends Comparable<Delayed> {
    long getDelay(TimeUnit unit);
}

简单使用

自定义一个MyDelayed实现Delayed接口。

class MyDelayed implements Delayed {
   //延时时间
   long delayTime;
   //创建时间
   final long creatTime = System.currentTimeMillis();
   //时间单位, 默认Nanos
   TimeUnit timeUnit = TimeUnit.NANOSECONDS;
   public MyDelayed(long delayTime, TimeUnit timeUnit) {
       this.delayTime = delayTime;
       this.timeUnit = timeUnit;
   }
   public MyDelayed(long delayTime) {
       this.delayTime = delayTime;
   }
   @Override
   public int compareTo(Delayed o) {
       if (this.getDelay() < o.getDelay(timeUnit))
           return -1;
       else if (this.getDelay() > o.getDelay(timeUnit))
           return 1;
       else
           return 0;
   }
   @Override
   public long getDelay(TimeUnit unit) {
       return unit.convert(creatTime - System.currentTimeMillis() + delayTime, timeUnit);
   }
   public long getDelay() {
       return this.getDelay(timeUnit);
   }
}

测试

定义两个Mydelayed延时时间都为10000(毫秒)。

@Test
public void testDelayed() throws InterruptedException {
    MyDelayed myDelayedFirst = new MyDelayed(10000);
    Thread.sleep(3000);
    MyDelayed myDelayedEnd = new MyDelayed(10000);
    Thread.sleep(3000);
	System.out.println("myDelayedFirst=>" + myDelayedFirst.getDelay());
    System.out.println("myDelayedEnd=>" + myDelayedEnd.getDelay());
}

image-20220506002018297.png
除去程序本身的运行时间,一个是4000一个是7000差不多。


属性

//锁
private final transient ReentrantLock lock = new ReentrantLock();
//基于PriorityQueue实现,使用其存储数据
private final PriorityQueue<E> q = new PriorityQueue<E>();
//指定等待队列头部元素的线程,以避免不必要的定时等待
private Thread leader = null;
//等待队列
private final Condition available = lock.newCondition();

说明:Condition的await()和awaitNanos()这两个等待方法区别

线程状态区别:

  • await()方法,会使线程进入WAITING状态。
  • awaitNanos(Long long)方法,当超时时间未过会使线程进入TIMED_WAITING状态,当超时时间过了线程并未获取锁资源进入WAITING状态。

锁状态以及cpu时间片

  • 两个方法都会释放锁以及交出cpu分配的时间片。

唤醒机制

  • await()方法必须等待其他线程调用signal()方法才可以被唤醒、并且竞争获取锁才可以继续执行。
  • awaitNanos()方法
    ①在超时时间内其他线程调用signal()方法会立刻唤醒等待线程、并且竞争获取锁才可以继续执行。
    ②超过超时时间但其他线程未释放锁资源,该线程会继续等待,直到其他线程释放锁资源、且竞争获取锁才可以继续执行。
    ③其他线程可以无需调用signal()方法,当超时时间过了以后,只要锁资源完全释放,且竞争获取锁就可以继续执行。

构造器

很简单的两个构造器,一个有初始值的、一个没有初始值的。

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

添加方法

主要看这个offer(E e)方法。

  • add() 会调用 offer()。添加成功会返回true。
  • put() 会调用offer()
public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //节点加入PriorityQueue队列,并调整顺序,延时时间最短的在数组首节点
        q.offer(e);
        //当前所添加节点,最小(延时时间最短)。通知leader线程唤醒(减少不必要等待),去拿取最新的。
        if (q.peek() == e) {
            leader = null;
            available.signal();
        }
        return true;
    } finally {
        lock.unlock();
    }
}

获取方法

基本上顺序是:①q.peek()获取元素②判断是否达到延时时间。对于未达到超时时间的分为等待和直接返回null。

不会等待的

  • peek()方法
    无视超时时间是否达到,直接返回队列首个节点(不会对队列做修改)
  • poll()方法
    如果队列为空或未达到超时时间,返回null。
    否则调用PriorityQueue.poll()出队。
 public E peek() {
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
         return q.peek();
     } finally {
         lock.unlock();
     }
 }
 ​
 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();
     }
 }

会等待的:

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)
                    available.await();
                else {
                    //指定等待队列获取头部元素的线程为当前线程
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        //等待。达到超时时间且可以获取锁继续执行
                        available.awaitNanos(delay);
                    } finally {
                        //leader线程唤醒,设置leader为空
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        //leader已经获取了节点,唤醒等待队列下的线程
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

这个东西呢能做什么

可以有一些延时或超时的场景的使用

  • 客户端空闲一段时间,服务器关闭连接
  • 订单超时时间内之内未支付,取消订单

我们以订单超时为例:
同时可以使用remove()方法来模拟订单支付,跳出支付等待队列。

package com.roily.queue.blockingqueue.delayQueue;
public class DelayQueueDemo {
    //待取消订单队列
    static DelayQueue<Order> orderQueue = new DelayQueue<>();

    public static void main(String[] args) throws InterruptedException {
        Thread addT = new Thread(() -> {
            //不停生成订单
            String orderNo = "YYC" + System.currentTimeMillis();
            long delayTime = 0L;
            while (true) {
                //随机生成延时时间
                delayTime = (long) (Math.random() * 10) * 1000;
                //生成订单
                Order order = new Order(orderNo, delayTime);
                if (orderQueue.add(order)) {
                    try {
                        System.out.println(order.getOrderNo()+"号订单添加到队列,等待时长为=>"+order.getDelay());
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                orderNo = "YYC" + System.currentTimeMillis();
            }


        }, "线程T1:");

        Thread takeT = new Thread(() -> {
            //不停取

            while (true) {
                try {
                    Order order = orderQueue.take();
                    System.out.println(order.getOrderNo() + "号订单被取消");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }, "线程T2:");

        takeT.start();

        addT.start();
        //主线程一直运行
        while (true);

    }

    //模拟支付完成 退出等待队列
    public static void removeOrder() {
        String hadPay = "线程T2:1";
        //线程T2:9
        orderQueue.remove(new Order(hadPay,0l));

        System.err.println(hadPay + "号订单已经完成支付、退出等待队列");
    }
}

@Data
class Order implements Delayed {
    //订单创建时间
    private final Long createTime = System.currentTimeMillis();
    //默认订单,默认时间单位
    private static TimeUnit overTimeUnit = TimeUnit.MILLISECONDS;
    //超时时间
    private Long delayedTime;
    //订单编号
    private String orderNo;
    public Order(String orderNo, Long delayedTime) {
        this.orderNo = orderNo;
        this.delayedTime = delayedTime;
    }
    @Override
    public long getDelay(TimeUnit unit) {
        long delayTime = unit.convert(delayedTime - (System.currentTimeMillis() - createTime), overTimeUnit);
        return delayTime;
    }
    public long getDelay() {
        return getDelay(overTimeUnit);
    }
    @Override
    public int compareTo(Delayed o) {
        return this.getDelay(overTimeUnit) >= o.getDelay(overTimeUnit) ? 1 : -1;
    }
}