java定时任务实现原理及扩展思路-----Timer
java定时任务实现原理-----Timer
近期在项目中使用到了java的定时任务, 但是是非固定周期的重复性定时任务, 于是想对最基础的Timer实现原理探究一下.
定时任务需要哪些组件?
大致我们能够想到, 一个定时任务至少需要三个组件:
1. 承载业务的对象 ----- 被调度对象
2. 存储容器 ----- 暂时存储被调度对象, 这个容器内的元素应该是有顺序的,
3. 调度器 ------ 管理存储容器, 通过限时进入等待与被唤醒实现定时
在java中, 最简单的定时任务实现就是Timer.简单Demo如下:
public static void main(String[] args) {
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("延迟10s");
System.out.println(System.currentTimeMillis());
//结束定时器
throw new RuntimeException();
}
};
Timer timer = new Timer();
timer.schedule(task1, 10000);
System.out.println(System.currentTimeMillis());
}
TimerTask 承载业务, 其中定义了几个状态,防止但实例被多线程调度
Timer中聚合了 TaskQueue和TimerThread.
TaskQueue 储存TimerTask, 内部存储的元素具有有序, 通过TaskQueue#getMin获取延迟时间最小的任务
TimerThread 主要完成调度工作, 主要代码如下:
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
//1. 因为TaskQueue内部没有实现线程安全, 所以对queue的修改以及查询都需要加锁
//2. 当TimerThread在queue实例上等待的时候, 调用queue对象的的notify可以唤醒
// TimerTask
synchronized(queue) {
// Wait for queue to become non-empty
while (queue.isEmpty() && newTasksMayBeScheduled)
//队列为空和当前线程依然需要存活, 就继续等待
queue.wait();
if (queue.isEmpty())
break; // Queue is empty and will forever remain; die
// Queue nonempty; look at first evt and do the right thing
long currentTime, executionTime;
//获取距离当前最近的一个任务
task = queue.getMin();
synchronized(task.lock) {
//对于已经取消的任务,直接忽略即可
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue; // No action required, poll queue again
}
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
//如果拿到的任务预计执行时刻已经过了, 那么后续将会立即执行
if (taskFired = (executionTime<=currentTime)) {
//如果是非重复性任务
if (task.period == 0) { // Non-repeating, remove
//非重复性需要从队列里面移除任务
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
//重复性任务只需要调整这个实例在队列中的位置
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
//如果任务的预执行时刻还未到, 则当前线程进入限时等待.
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime);
}
if (taskFired) // Task fired; run it, holding no locks
task.run();
} catch(InterruptedException e) {
}
}
}
1. TimerTask 启动之后就会进入等待, 直到有其他线程唤醒为止.
2. TimerTask 被唤醒之后, 拿到任务, 如果是周期性任务, 就调整任务的下一次执行时间,
如果是非周期性任务, 直接删除即可(这样也就是延时任务与周期性任务的实现)
3. 如果还没到任务预定的执行时刻, TimerThread 会进入限时等待状态, 后续有其他线程调用schedul()这类方法,
有机会使TimerTask提前被唤醒, 如果被唤醒, 那么将进入下一次循环.(这个使得先提交延时长的任务, 后提交延时短
的任务, 但是依然能按照预定延时实现)
Timer的弊端:
- 单线程不可靠
- 对于重复执行的任务, 周期只能固定
- 重复执行的任务, 重复的次数不好控制, 不好实现有条件的重复
扩展思路:
- 单线程不可靠如何扩展?
可以使用线程池来完成调度 - 重复性任务周期固定?
首先需要扩展TimerTask, 增加一个getPeriod()方法, 这个方法的返回值可以随着当前实例被调用次数而改变,
这样即可实现自定义周期扩展
如上代码, 使得重复性任务只能以固定周期执行. 如果将 task.period 调用该为 task.getPeriod(),
我们就可以通过扩展Timer实现getPeriod()方法使得:
- 不固定周期的重复性任务
- 可控制重复次数的重复任务
- 有条件的重复任务
- 完全无规律就按照给的时间执行的重复任务
...