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