DelayQueue 简介

由优先级堆支持的、基于时间的调度队列,内部基于无界队列PriorityQueue实现,而无界队列基于数组的扩容实现。

队列创建

BlockingQueue<String> blockingQueue = new DelayQueue();

要求

入队的对象必须要实现Delayed接口,而Delayed集成自Comparable接口

应用场景

对缓存超时的数据进行移除

  • 当向缓存中添加key-value对时,如果这个key在缓存中存在并且还没有过期,需要用这个key对应的新过期时间。
  • 为了能够让DelayQueue将其已保存的key删除,需要重写实现Delayed接口添加到DelayQueue的DelayedItem的hashCode函数和equals函数。
  • 当缓存关闭,监控程序也应关闭,因而监控线程应当用守护线程

代码如下:

public class Cache<K,V> {
    public ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>();

    public DelayQueue<DelayedItem<K>> queue = new DelayQueue<>();

    public void put(K k , V v,long liveTime){
        V v2 = map.put(k,v);
        DelayedItem<K> tmpItem = new DelayedItem<K>(k,liveTime);
        if (v2 != null) {
            queue.remove(tmpItem);
        }
        queue.put(tmpItem);
    }

    public Cache(){
        Thread t = new Thread(){
            @Override
            public void run(){
                dameonCheckOverdueKey();
            }
        };
        t.setDaemon(true);
        t.start();
    }

    public void dameonCheckOverdueKey(){
        while (true) {
            DelayedItem<K> delayedItem = queue.poll();
            if (delayedItem != null) {
                map.remove(delayedItem.getT());
                System.out.println(System.nanoTime()+" remove "+delayedItem.getT() +" from cache");
            }
            try {
                Thread.sleep(300);
            } catch (Exception e) {
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Random random = new Random();
        int cacheNumber = 10;
        int liveTime = 0;
        Cache<String, Integer> cache = new Cache<String, Integer>();
        for (int i = 0; i < cacheNumber; i++) {
            liveTime = random.nextInt(3000);
            System.out.println(i+"  "+liveTime);
            cache.put(i+"", i, random.nextInt(liveTime));
            if (random.nextInt(cacheNumber) > 7) {
                liveTime = random.nextInt(3000);
                System.out.println(i+"  "+liveTime);
                cache.put(i+"", i, random.nextInt(liveTime));
            }
        }

        Thread.sleep(3000);
        System.out.println();
    }


}


class DelayedItem<T> implements Delayed{
    private T t;

    private long liveTime;

    private long removeTime;

    public DelayedItem( T t, long liveTime ) {
        this.t = t;
        this.liveTime = liveTime;
        this.removeTime = TimeUnit.NANOSECONDS.convert(liveTime,TimeUnit.NANOSECONDS) + System.nanoTime();
    }

    @Override
    public long getDelay( TimeUnit unit ) {
        return unit.convert(removeTime - System.nanoTime(),unit);
    }

    @Override
    public int compareTo( Delayed o ) {
        if (o == null) return 1;
        if (o == this) return  0;
        if (o instanceof DelayedItem){
            DelayedItem<T> tmpDelayedItem = (DelayedItem<T>)o;
            if (liveTime > tmpDelayedItem.liveTime ) {
                return 1;
            }else if (liveTime == tmpDelayedItem.liveTime) {
                return 0;
            }else {
                return -1;
            }
        }
        long diff = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        return diff > 0 ? 1:diff == 0? 0:-1;
    }
    @Override
    public int hashCode(){
        return t.hashCode();
    }

    @Override
    public boolean equals(Object object){
        if (object instanceof DelayedItem) {
            return object.hashCode() == hashCode() ?true:false;
        }
        return false;
    }

    public T getT( ) {
        return t;
    }

    public void setT( T t ) {
        this.t = t;
    }
}

任务超时处理

比如 在京东商城,买了一件东西,提交了订单,但是还没付款,30分钟后如果没有付款,则会自动取消

实现方式如下:

  1. 使用数据库定时任务,每隔几秒扫描订单表,找出超时订单后关闭
  2. 使用spring的@Scheduled注解启动定时任务或者使用Quartz任务管理器,定时触发任务,处理超时订单。
  3. 使用消息中间件,Active或者RocketMQ提供了延迟消息队列,下单后往延迟消息队列中发消息,超时后,消费端会接收到一条延迟的 订单消息,并做相应处理
  4. 使用DelayQueue来实现

下面我们介绍使用DelayQueue 来怎么实现?

@Component
@Lazy(false)
public class DelayOrderComponent{
    
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderService orderService;
    
    private static DelayQueue<OrderMessage> delayQueue =  new DelayQueue<OrderMessage>();
    
    @PostConstruct
    public void init() throws Exception {
        /**初始化时加载数据库中需处理超时的订单**/
        List<OverTimeOrder> orderList = orderMapper.selectOverTimeOrder();
        for (int i = 0; i < orderList.size(); i++) {
            OrderMessage orderMessage = new OrderMessage(propertyCarList.get(i).getOrderId(),propertyCarList.get(i).getCreateTime());
            this.addToOrderDelayQueue(orderMessage);
        }
        
        /**启动一个线程,去取延迟消息**/
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                OrderMessage message = null;
                while (true) {
                    try {
                        message = delayQueue.take();
                        //处理超时订单
                        orderService.closeOverTimeOrder(message.getOrderId());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
    
    /**加入延迟消息队列**/
    private boolean addToOrderDelayQueue(OrderMessage orderMessage){
        return delayQueue.add(orderMessage);
    }
    
    
    /**从延迟队列中移除**/
    private void removeToOrderDelayQueue(OrderMessage orderMessage){
        if(Tools.isEmpty(orderMessage)){
            return;
        }
        for (Iterator<OrderMessage> iterator = delayQueue.iterator(); iterator.hasNext();) {
            OrderMessage queue = (OrderMessage) iterator.next();
            if(orderMessage.getOrderId().equals(queue.getOrderId())){
                delayQueue.remove(queue);
            }
        }
    }
    
}

(1) init()方法:在对象实例化的时候,加载数据库中的需要延时处理的订单,并启动一个线程等待处理超时出队列的订单。
(2) 对外提供加入消息到延迟队列方法 addToOrderDelayQueue ,当下单的时候加入延迟消息队列。
(3) 对外提供删除延迟消息方法 removeToOrderDelayQueue ,当用户主动取消订单,或者支付成功后使用。

public class OrderMessage implements Delayed {
    
    private final static long DELAY = 15*60*1000L;//默认延迟15分钟
    
    private final String orderId;//订单号
    
    private final long startTime ;//开始时间

    private final long expire ;//到期时间
    
    private final Date now; //创建时间
    
    private final String orderMsg;//订单其他信息JSON方式保存,备用字段
    
    public OrderMessage(String orderId, String startTimeStr14 ,long secondsDelay) {
        super();
        this.orderId = orderId;
        this.startTime = DateUtils.toDateTime14(startTimeStr14).getTime();
        this.expire = startTime + (secondsDelay*1000);
        this.now = new Date();
        this.orderMsg="";
    }
    
    public OrderMessage(String orderId, String startTimeStr14, String orderMsg ,long secondsDelay) {
        super();
        this.orderId = orderId;
        this.startTime = DateUtils.toDateTime14(startTimeStr14).getTime();
        this.expire = startTime + (secondsDelay*1000);
        this.orderMsg = orderMsg;
        this.now = new Date();
    }
    
    public OrderMessage(String orderId, String startTimeStr14) {
        super();
        this.orderId = orderId;
        this.startTime = DateUtils.toDateTime14(startTimeStr14).getTime();
        this.expire = startTime + DELAY;
        this.now = new Date();
        this.orderMsg="";
    }
    
    public OrderMessage(String orderId, String startTimeStr14, String orderMsg) {
        super();
        this.orderId = orderId;
        this.startTime = DateUtils.toDateTime14(startTimeStr14).getTime();
        this.expire = startTime + DELAY;
        this.orderMsg = orderMsg;
        this.now = new Date();
    }

    @Override
    public int compareTo(Delayed o) {
        return (int) (this.getDelay(TimeUnit.MILLISECONDS) -o.getDelay(TimeUnit.MILLISECONDS));
    }
    
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.expire - System.currentTimeMillis() , TimeUnit.MILLISECONDS);
    }

    public String getOrderId() {
        return orderId;
    }

    public String getOrderMsg() {
        return orderMsg;
    }
    
    public Date getNow() {
        return now;
    }

    public long getStartTime() {
        return startTime;
    }

    public long getExpire() {
        return expire;
    }

}

1、 延迟消息必须实现 Delayed 接口,并实现compareTo和getDelay接口。
2、此处我定义了多个构造方法,方便在多种场景下使用。

public static void main(String[] args) {
        final DelayQueue<OrderMessage> delayQueue =  new DelayQueue<OrderMessage>();
        delayQueue.add(new OrderMessage(""+1, DateUtils.curDateTimeStr14(),"3秒后执行"));
        delayQueue.add(new OrderMessage(""+2, DateUtils.curDateTimeStr14(),"4秒后执行"));
        delayQueue.add(new OrderMessage(""+3, DateUtils.curDateTimeStr14(),"8秒后执行"));
        
        /**启动一个线程,处理延迟消息**/
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                OrderMessage message = null;
                while (true) {
                    try {
                        message = delayQueue.take();
                        System.out.println(new Date()+"  处理延迟消息:  "+JSON.toJSONString(message));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

源码分析

首先看一下DlayedQueue源码:

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {

    private final transient ReentrantLock lock = new ReentrantLock();
    private final PriorityQueue<E> q = new PriorityQueue<E>();

    // ...

}

这是一小部分源码,从这段源码可以看出,DelayedQueue中处理的是实现Delayed接口的任务,DelayedQueue使用lock来实现线程同步,使用PriorityQueue来管理任务。那么Delayed接口是什么呢?

public interface Delayed extends Comparable<Delayed> {

    long getDelay(TimeUnit unit);
}

这是Delayed接口的官方注释,意思是:一个混合风格的接口,为创建给定延迟的任务。(翻译的不太好,请纠正);其中有两个重要的方法compareTo和getDelay,第一个是比较两个任务的延迟时间进行排序,第二个方法用来获取延迟时间。

priorityQueue是一种优先级队列,这里优先级就是延迟时间,也就是说进入队列的任务安装优先级进行排序,延迟时间最短的在队列前面,先被处理,也就是说,每次从队列中取出的任务都将是到期的任务。比如我们实现一个缓存,当某个key-value对超期了,我们就可以从队列前取出,然后进行销毁操作。