一、概述
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); } } }
四、使用场景
比如做系统发布后,做一些监控的任务,参数的巡检,可以使用延时队列。