26. DelayQueue介绍
1. DelayQueue介绍
先看下类结构:
1.放到DelayQueue的对象必须实现Delayed接口
2.通过实现Delayed接口的getDelay方法返回延迟的时间,放入DelayQueue的对象在超过这个时间后才可以从队列中取出,实现延迟的效果。
3.通过实现Comparable接口进行排序,决定从队列中先取出哪个对象,通常就是按照getDelay方法的结果排序,这样延迟最短的可以先取出来。
2. 应用场景
对于DelayQueue的应用场景,要先理解延迟和失效在特定场景下可能是两个不同的概念,延迟就是延迟多少时间后执行,而失效不一定等于延迟,例如缓存未使用的失效时间是2分钟,超过2分钟就清掉缓存,但在失效前缓存被使用了,这个失效时间就会被延长。看下图:
DelayQueue里的对象一旦放入,就不会重新排序,除非删除该元素,重新放入,而DelayQueue是个阻塞的优先级队列,队首的元素不被取出,后面的元素也不会被取出,如果队尾的元素延迟时间变化,减少了,但是没有重排序,那么也会因为队首元素未取出而取不出来,因此DelayQueue并不适合元素延迟时间会动态变化的场景,例如缓存,闲置的数据库连接池等等,它们的失效时间都会因为被使用而延长。
当然,如果要通过删除失效时间已经变化的元素,再重新放入DelayQueue队列来重排序也不是不可以,但一般不会这么做,因为对于频繁使用缓存元素的场景,每次使用到缓存时都对队列做一次重排序这个代价太大;因此对于缓存和闲置资源池的清理,通常都是通过一个定时执行的线程来遍历所有元素判断是否超过失效时间来清理。
3. DelayQueue例子
实现任务延迟执行。
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class DelayQueueDemo {
// 定义一个可延迟执行的任务
private static class Task implements Delayed {
private String name;
private long start;
private long delayMillis;
Task(String name, long delayMillis) {
this.name = name;
this.delayMillis = delayMillis;
start = System.currentTimeMillis();
}
@Override
public long getDelay(TimeUnit unit) {
long result = unit.convert((start + delayMillis) - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
return result;
}
@Override
public int compareTo(Delayed o) {
return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
}
public void exec() {
System.out.println(name + " was called.");
}
}
private static class TaskTimer implements Runnable {
private static volatile TaskTimer instance;
DelayQueue<Task> delayQueue = new DelayQueue();
private TaskTimer() {}
public static TaskTimer getInstance() {
if (instance == null) {
synchronized (TaskTimer.class) {
if (instance == null) {
instance = new TaskTimer();
}
}
}
return instance;
}
public void add(Task task) {
delayQueue.put(task);
}
@Override
public void run() {
while (true) {
try {
Task task = delayQueue.take(); // 阻塞队列,从队首开始取任务
task.exec();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
TaskTimer taskTimer = TaskTimer.getInstance();
new Thread(taskTimer).start();
addTask("task1", 3000);
addTask("task2", 1500);
}
private static void addTask(String taskName, long delayMillis) {
Task task = new Task(taskName, delayMillis);
TaskTimer.getInstance().add(task);
}
}
打印日志:
task2 was called.
task1 was called.
task2在1.5秒后执行,task1在3秒后执行。
4. 小结
1.DelayQueue是一个阻塞的优先级队列。
2.DelayQueue队列元素在入队时排序,入队后即使元素的getDelay方法返回值变化,也不会重排序,除非重新入队,因此不太适合延迟时间会动态变化的场景,例如缓存释放,闲置连接池释放。
3.可以考虑通过DelayQueue实现限流场景,当请求太多处理不过来时,可以将部分请求放到延迟队列中,过一段时间后取出来执行。
4.开源定时器也能实现任务延迟执行的效果,相比之下DelayQueue更加轻量,可根据实际情况使用。