序
定时任务是现代应用开发一个常见需求。Java提供了多种机制来处理定时任务,从早期的java.util.Timer到现代的ScheduledThreadPoolExecutor,再到时间轮算法。本文将探讨这些机制演进背后的逻辑,以及对应的使用场景。
一、JDK Timer
JDK Timer是最基础的定时任务执行类,它允许你安排在未来的某个时间点执行一次性任务或者定期重复执行的任务。Timer使用单线程来管理所有任务,如果某个任务执行时间过长,会阻塞其他任务的执行,导致整体性能下降。
Timer的使用方式如下:
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
}
}, 10000, 1000); // 10s 后调度一个周期为 1s 的定时任务
Timer核心TaskQueue是基于最小堆数据结构实现的,任务schedule和获取时间复杂度是:O(logn)
public class Timer {
private final TaskQueue queue = new TaskQueue();
}
class TaskQueue {
private TimerTask[] queue = new TimerTask[128];
void add(TimerTask task) {
...
queue[++size] = task;
//最小堆调整
fixUp(size);
}
void removeMin(TimerTask task) {
...
queue[1] = queue[size];
queue[size--] = null; // Drop extra reference to prevent memory leak
fixDown(1);
}
}
二、DelayQueue + Thread
DelayQueue是一个无界阻塞队列,用于实现延迟或定时任务的调度。它内部使用优先级队列来存储实现了Delayed接口的对象。
相比Timer,支持了任务的优先级控制;DelayQueue是线程安全的,结合Thread使用,提供多线程任务执行的能力。但DelayQueue核心实现PriorityQueue也是基于最小堆实现的,新增/删除任务时间复杂度是:O(logn)。
三、ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor是Java并发包中一个更为强大的定时任务执行器。它不仅支持周期性执行任务,还允许使用不同的线程池来管理任务的执行,相当于堆DelayQueue + Thread方式做了一个门面,额外还提供了定时调度(添加任务)的能力。
ScheduledThreadPoolExecutor通过内部的DelayedWorkQueue来管理任务的延迟执行,这个队列本质上也是一个最小堆。
四、时间轮
时间轮是一种高效的任务调度算法,其基本思想是将时间划分为多个槽,每个槽代表一个时间间隔。任务按照其到期时间被放入相应槽位,通过指针的旋转(模拟时钟的走动)触发槽位中的任务执行。时间轮可以快速地插入、删除任务,同时利用循环数组的特性减少了内存消耗。
时间轮特别适合处理大量短周期定时任务,能显著提高系统的吞吐量和响应速度,但在时间
可以参考:Netty的HashedWheelTimer
五、多层时间轮
为了进一步提高时间轮的性能,出现了多层时间轮的设计。在多层时间轮中,不同的轮层处理不同时间粒度的任务。例如,第一层轮可能处理秒级的任务,而第二层轮处理分钟级的任务。这种设计可以减少轮的尺寸,同时保持对各种延迟时间任务的高效支持。
可以参考Kafka的实现。
还有多层时间轮的变种,存储主要分为2部分,短周期轮转表和长周期表,时间轮存储最近1h的数据,其他数据扁平化存储在长周期表中,长周期表的数据会流转到短周期轮转表中。
应用场景
JDK Timer:适用于简单的定时任务,但不适合高并发或者对于任务执行时效敏感的任务类型。
DelayQueue:适用于需要处理大量延迟任务的场景,如消息队列。
ScheduledThreadPoolExecutor:适用于需要周期性执行任务的高并发场景。
时间轮:适用于需要高效调度大量短延迟任务的场景。
多层时间轮:适用于需要处理不同时间粒度任务的复杂场景。
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » Java定时任务缘起
发表评论 取消回复