延时队列DelayQueue
延时队列DelayQueue的使用介绍
java.util.concurrent.DelayQueue
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E>
延时队列,最重要的特性就体现在它的延时属性上,跟普通的队列不一样的是,普通队列中的元素总是等着希望被早点取出处理,而延时队列中的元素则是希望被在指定时间得到取出和处理,所以延时队列中的元素是都是带时间属性的,通常来说是需要被处理的消息或者任务。延时队列就是用来存放需要在指定时间被处理的元素的队列
简介
- 以支持优先级无界队列的PriorityQueue作为一个容器,容器里面的元素都应该实现Delayed接口,在每次往优先级队列中添加元素时以元素的过期时间作为排序条件,最先过期的元素放在优先级最高。
- DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
DelayQueue实现了阻塞队列BlockingQueue接口,其中阻塞队列涉及到入队和出队的操作在队列不可用时的处理方式如下:
| 处理方式 | 抛出异常 | 返回特殊值 | 阻塞 | 超时退出 |
|---|---|---|---|---|
| 插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
| 移除 | remove() | poll() | take() | poll(time,unit) |
| 查看 | element() | peek() |
其中DelayQueue为无界队列;其入队和出队方法主要有以下几种:
入队:add和put方法均使用的offer方法进行入队操作,不会抛出异常也不会阻塞,offer(e,time,unit)同样使用的offer方法,它的另外两个入参被忽略未实际使用,
出队:
| 方法 | 入参 | 返回值 | 说明 |
|---|---|---|---|
| remove | 队列元素 | Boolean | 移除队列中的元素,返回移除是否成功标识 |
| poll | - | 出队元素 | 按优先级出队,无待出队元素返回null |
| take | - | 出队元素 | 按优先级出队,无元素出队时阻塞 |
| poll | time,unit | 出队元素 | 按优先级出队,无待出队元素等待一段时间后再次尝试出队,无元素可以出队时返回null |
简单使用示例
// 自定义一个延时任务封装类
public class DelayedTask<T> implements Delayed, Runnable {
/**
* 任务参数
*/
private final T taskParam;
/**
* 任务类型
*/
private final Integer type;
/**
* 任务函数
*/
private final Function<T, String> function;
/**
* 任务执行时刻,时间戳
*/
private final Long runTime;
public DelayedTask(T taskParam, Integer type, Function<T, String> function, Long runTime) {
this.taskParam = taskParam;
this.type = type;
this.function = function;
this.runTime = runTime;
}
@Override
public void run() {
if (taskParam != null) {
function.apply(taskParam);
}
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.runTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
// 以执行时间戳值的大小比较,越小优先级越高
if (o instanceof DelayedTask) {
return this.runTime.compareTo(((DelayedTask<?>) o).runTime);
} else {
return -1;
}
}
}
测试主类:
public class DelayQueueTest {
public static final int SIZE = 10;
public static void main(String[] args) {
//初始化线程池
BlockingQueue<Runnable> arrayBlockingQueue = new ArrayBlockingQueue<>(10);
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 10, TimeUnit.MILLISECONDS,
arrayBlockingQueue, Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
DelayQueue<DelayedTask<String>> delayTaskQueue = new DelayQueue<>();
// 定义任务执行方法
Function<String, String> function = param -> {
StringBuilder builder = new StringBuilder(Thread.currentThread().getName());
builder.append(":").append(System.currentTimeMillis()).append(">>> log >>>")
.append(param.getClass().getSimpleName()).append(":").append(param);
System.out.println(builder);
return builder.toString();
};
//模拟10个延迟任务
long now = System.currentTimeMillis();
for (byte i = 0; i < SIZE; i++) {
// 设置每隔3s执行一个任务
Long runTime = now + 3000 * i;
String taskParam = "执行第" + i + "个任务";
delayTaskQueue.put(new DelayedTask<>(taskParam, 1, function, runTime));
}
while (delayTaskQueue.size() != 0) {
try {
//从延迟队列中取值,如果没有对象过期则取到null
DelayedTask<String> delayedTask = delayTaskQueue.poll();
if (delayedTask != null) {
threadPool.execute(delayedTask);
}
} catch (Exception e) {
e.printStackTrace();
}
}
threadPool.shutdown();
}
}
输出结果:
pool-1-thread-1:1634822371554>>> log >>>String:执行第0个任务
pool-1-thread-2:1634822374552>>> log >>>String:执行第1个任务
pool-1-thread-3:1634822377552>>> log >>>String:执行第2个任务
pool-1-thread-4:1634822380552>>> log >>>String:执行第3个任务
pool-1-thread-5:1634822383552>>> log >>>String:执行第4个任务
pool-1-thread-1:1634822386552>>> log >>>String:执行第5个任务
pool-1-thread-2:1634822389552>>> log >>>String:执行第6个任务
pool-1-thread-3:1634822392552>>> log >>>String:执行第7个任务
pool-1-thread-4:1634822395552>>> log >>>String:执行第8个任务
pool-1-thread-5:1634822398552>>> log >>>String:执行第9个任务
常见使用场景
- 重试机制:比如当调用接口失败后,把当前调用信息放入delay=10s的元素,然后把元素放入队列,那么这个队列就是一个重试队列,一个线程通过take()方法获取需要重试的接口,take()返回则接口进行重试,失败则再次放入队列,同时也可以在元素加上重试次数。
- 周期任务
- 订单超时未支付取消
- 连接池关闭空闲一段时间后的连接
- 清理过期的内存数据

浙公网安备 33010602011771号