java定时任务的另一种实现---延时队列(DelayQueue)

文章目录

    • java定时任务的另一种实现---延时队列(DelayQueue)
      • 一、DelayQueue介绍
      • 二、实例演示
        • 1、定义一个Delayed子类
        • 2、main方法
        • 3、常用方法说明
        • 4、take()方法原理解析
      • 三、相关问题

java定时任务的另一种实现—延时队列(DelayQueue)

一、DelayQueue介绍

类签名:

public class DelayQueue extends AbstractQueue implements BlockingQueue

使用场景:
在我们的业务系统中,一般会有“再多久后…就怎样”的业务,比如,一个订单在15分钟后未被支付,则需要被释放。为了完成此类业务,比较常见的实现方式为定时任务扫描:每分钟扫描一次“15分钟未付款”的订单。现在我们有了新的实现方式,那就是延迟队列—DelayQueue。

从签名“”可知,DelayQueue容器将范型“E”限定为了Delay的子类,所以DelayQueue容器的所操作的元素,必须为Delay的子类。

二、实例演示

1、定义一个Delayed子类

class DelayedElement implements Delayed {
     

	private long mills;
	
    public DelayedElement(Integer min) {
     
        this.mills = System.currentTimeMillis() / 1000 + (60 * min);
    }
    
	/**
     * 当方法返回小于或等于0的数值时, 元素将会被消耗
     * @param unit
     * @return
     */
 	@Override
    public long getDelay(TimeUnit unit) {
     
        return mills - (System.currentTimeMillis() / 1000);
    }

	/**
     * DelayQueue底层是优先队列,需要实现compareTo方法,
     * 用于在入列的时候与容器中的数据进行比较,以便确定元素位置。
     */
    @Override
    public int compareTo(Delayed o) {
     
        return (int)(mills - ((DelayedElement)o).getMills());
    }
	
	public long getMills() {
     
        return mills;
    }

    public void setMills(long mills) {
     
        this.mills = mills;
    }
}

2、main方法

public static void main(String[] args) throws InterruptedException {
     

      DelayQueue<DelayedElement> queue = new DelayQueue<>();
      
      //一分钟后输出
      queue.add(new DelayedElement(1));
      //两分钟后输出
      queue.add(new DelayedElement(2));
      
      System.out.println(queue.take());
      System.out.println(queue.take());
}

在执行第一个queue.take()语句后,程序会进行阻塞,直到有delayed元素过期。

3、常用方法说明

take()
方法签名:public E take() throws InterruptedException{
     ...}
方法说明:该获取并且移除队列中的头元素。如果“延迟元素”还未过期,则等待,直至可用。如果队列为空,
也等待,直至获取到delay元素。阻塞方法,如果未获取到目标值,则会一直阻塞。
clear()
方法签名:public void clear(){
     ...}
方法说明:清空队列。执行该方法后,所有元素都将被移出队列。
add()
方法签名:public boolean add(E e){
     ...}
方法说明:向延迟队列插入元素e。如果成功,则返回true
put()
方法签名:public boolean put(E e){
     ...}
方法说明:向延迟队列插入元素e。如果成功,则返回true
peek()
方法签名:public E peek(){
     ...}
方法说明:返回队列中的头元素,但不将元素从队列中移除。该方法无论元素是否过期,均返回。该方法为非阻塞方法。
poll()
方法签名:public E poll(){
     ...}
方法说明:返回队列中的头元素并且将元素从队列中移除。如果队列中没有已过期的元素,则返回null。

4、take()方法原理解析

 	public E take() throws InterruptedException {
     
        final ReentrantLock lock = this.lock;
        //获取锁(其它线程进入该代码块则等待)
        lock.lockInterruptibly();
        try {
     
            for (;;) {
     
            	//q是PriorityQueue对象,q.peek():获取队列头结点,但不从
            	//队列中移除元素,如果队列为空,则返回null。
                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
                    //Thread leader = null;
                    if (leader != null)
                    	//非leader线程,使线程无限期休眠,用于多线程减少不必要的等待
                        available.await();
                    else {
     
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
     
                        	//使当前线程休眠,在delay时间后被唤醒
                            available.awaitNanos(delay);
                        } finally {
     
                        	//被唤醒后执行
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
     
        	//如果leader已重置并且队列有数据,则唤醒其它线程
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

三、相关问题

1、当DelayQueue容器为空时,执行take()方法会使当前线程休眠,请问当DelayQueue容器添加元素时,休眠的线程是如何被唤醒?

通过源码可以发现:在添加元素后,会执行available.signal()方法。

	public boolean add(E e) {
     
        return offer(e);
    }
     
    public boolean offer(E e) {
     
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
     
            q.offer(e);
            if (q.peek() == e) {
     
                leader = null;
                available.signal();
            }
            return true;
        } finally {
     
            lock.unlock();
        }
    }

你可能感兴趣的:(java基础,多线程,java,queue)