Java之定时器Timer和定时任务TimerTask应用以及原理解读
作者:zhangbeizhen18
记录:272
场景:Java JDK自带的定时器Timer和定时任务TimerTask应用以及原理简析。
在JDK工具包:java.util中可以找到源码,即java.util.Timer和java.util.TimerTask。
TimerTask实现Runnable接口的run方法。
Timer的属性TimerThread thread继承Thread。因此,Timer天生就具备多线程属性。
这个轻量级的定时器和定时任务,在多线程的场景中使用极其便利和灵活。
版本:
JDK 1.8 Spring Boot 2.6.3 Spring Framework 5.3.15
1.基础
Timer和TimerTask成对出现,Timer是定时器,TimerTask是定时任务。换句话说,定时任务TimerTask是给定时器Timer执行的具体任务。
在JDK 1.8中TimerTask是抽象类(abstract),使用者继承TimerTask,并实现抽象方法run()方法即可。TimerTask实现Runnable接口的run()。
1.1 TimerTask
TimerTask,即java.util.TimerTask,一个抽象类(abstract修饰的类),实现(implements)可被线程执行的Runnable接口。
TimerTask需关注的内容。
(1)属性:final Object lock,多线程同步时使用。在Timer的TimerThread中同步锁synchronized会使用。
(2)属性:int state,标识TimerTask的当前状态。包括VIRGIN,SCHEDULED,EXECUTED,CANCELLED。
(3)属性:long nextExecutionTime,下一次执行的时间。
(4)属性:long period,重复任务的执行频率。
(5)方法:boolean cancel(),取消任务,实际标记任务状态state为CANCELLED。
(6)方法:scheduledExecutionTime(),获取调度时间。
(7)方法:protected TimerTask(),无参构造方法。
(8)方法:abstract void run(),线程执行具体函数,使用者的具体业务任务就在这个方法中写。
1.2 TaskQueue
TaskQueue,即java.util.TaskQueue。TaskQueue维护一个属性TimerTask[] queue,这是Timer的任务线程操作的底层队列。在Timer中有TaskQueue属性,是给任务线程TimerThread使用的。
TaskQueue需关注的内容。
(1)属性:TimerTask[] queue,队列中存放TimerTask。Timer的TimerThread通过遍历queue,取出TimerTask,根据TimerTask属性来确定当前是否执行。。
注意:使用者不需要直接操作TimerTask[] queue,它是交给Timer的TimerThread操作。
1.3 TimerThread
TimerThread,即java.util.TimerThread。继承Thread类。因此,天生就具备多线程属性。
TimerThread需关注的内容。
(1)属性:TaskQueue queue,TimerThread操作queue来找到当前需要执行的定时任务TimerTask。根据执行策略执行定时任务TimerTask中的run方法。
(2)方法:void run(),是TimerThread从Thread中继承的方法。线程执行的具体内容,必须在这个方法中写入或者在这个方法中被调用。TimerThread实现细节方法是void mainLoop()。在run方法中会调用mainLoop(),这样定时任务就被任务线程调用到。
(3)方法:void mainLoop(),TimerThread中逻辑细节落地方法。本方法内容在while (true)中,只要队列有任务就会无限循环。方法核心内容就是遍历TimerThread的属性:TaskQueue queue,找到符合条件的需执行的TimerTask,并执行TimerTask的run方法。
(4)方法: void start(),此方法是TimerThread从Thread继承来的,作用就是启动线程。TimerThread的start()方在Timer创建时就会调用。Timer创建后,TimerThread就启动了。
1.4 Timer
Timer,即java.util.Timer。使用者操定时器就是操作这个类的任务调度方法。
Timer的需关注的内容。
(1)属性:final TaskQueue queue,Timer存放所有的定时任务的队列,定时器Timer的核心就是遍历这个队列,找到当前时间下,符合条件的需执行的定时任务TimerTask。
(2)属性:final TimerThread thread,Timer执行定时任务的后台线程。定时任务具体执行逻辑在TimerThread的run方法中。TimerThread 线程在Timer的构造函数中会调用TimerThread的start()启动线程。
(3)构造函数
- 1>Timer(),无参构造函数。
- 2>Timer(boolean isDaemon),有参构造函数。设置定时器线程是否为守护线程。就是设置属性:TimerThread thread,调用Thread的setDaemon()方法。
- 3>Timer(String name),有参构造函数。设置线程名称。
- 4>Timer(String name, boolean isDaemon)。有参构造函数。设置线程名称和设置是否为守护线程。
(4)任务调度函数
- 1>任务调度函数
操作Timer定时器,实际就是操作如下任务调度函数。
public void schedule(TimerTask task, long delay); public void schedule(TimerTask task, Date time); public void schedule(TimerTask task, long delay, long period); public void schedule(TimerTask task, Date firstTime, long period); public void scheduleAtFixedRate(TimerTask task, long delay, long period); public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period);
- 2>任务调度函数(sched)
任务调度函数都会调用一个私有方法sched。
private void sched(TimerTask task, long time, long period);
TimerTask task
:继承抽象类TimerTask,重写abstract void run()方法,把具体业务任务写在这个方法里。long time
:调度任务的起始时间值。调度是从这个时间为计算起点。long period
:任务调度频率。
把定时任务TimerTask添加到队列,就是在这个方法中实现。
(5)方法:void cancel(),取消队列里所有任务。
(6)方法:void purge(),清除队列里所有任务。
2.Timer原理解析
Timer原理解析,来自JDK中java.util.Timer源码逻辑。如有疑惑,可直接翻阅源码,便一目了然。
2.1 添加定时任务
把TimerTask添加到Timer内部的TaskQueue中。
2.2.1 自定义实现TimerTask的 run()
自定义实现抽象类TimerTask的 run()方法,即任务需要执行的业务逻辑写在run方法中。
2.2.2 创建定时器Timer
创建定时器Timer,会做三件核心事情。
(1)创建任务队列:TaskQueue queue。
(2)创建定时器线程:TimerThread thread。
(3)启动定时器线程:执行TimerThread的start()方法,即后台循环扫描的定时器线程启动。
2.2.3 调用Timer任务调度函数
添加定时任务就在调用Timer任务调度方法中完成。
(1)设置定时器任务调度参数。
(2)把自定义任务传递给任务调度函数。
2.2.4 Timer任务调度函数核心逻辑
Timer暴露给使用者的所有任务调度函数,最终都会落地在一个私有方法,即
void sched(TimerTask task, long time, long period);
核心逻辑如下:
(1)同步锁:synchronized(queue),锁住队列。队列所有操作在此锁内完成。
(2)同步锁:synchronized(task.lock),锁住任务。
此锁内主要操作:设置TimerTask任务的执行时间,执行频率,任务状态。操作完成立即解锁。
(3)队列TaskQueue queue,把设置好的TimerTask添加到TaskQueue 中。
(4)解除同步锁:synchronized(queue)。
2.2 执行定时任务
执行定时任务,在Timer的定时器线程TimerThread thread中,即线程的run()方法中,最终落地方式是 mainLoop()。注意mainLoop()是在run()方法中调用,是逻辑集中写在mainLoop()中,增加代码易读性。
2.2.1 while (true)入口
mainLoop()入口是while (true),即使循环扫描。
2.2.2 同步锁:synchronized(queue)
同步锁:synchronized(queue),锁住队列,一个Timer共用一个队列,因此使用synchronized有效。
2.2.3 判断队列是否为空
使用 while (queue.isEmpty() && newTasksMayBeScheduled)判断队列是否为空,如果为空,则 queue.wait()等待,注意wait()是java.lang.Object的方法,因此,此时while在卡主状态,直到queue.notify()被调用,才会继续。
2.2.4 确认队列为空跳出循环
使用if (queue.isEmpty()),判断队列确定为空了,那么就break跳出循环,其实任务线程就优雅结束了。
2.2.5 取出一个任务TimerTask
取出一个任务:task = queue.getMin();
2.2.6 同步锁:synchronized(task.lock),操作任务
使用同步锁:synchronized(task.lock),锁住任务。判断任务是否可执行。
(1)判断任务状态。
(2)取当前系统时间:System.currentTimeMillis()。
(3)取出的任务执行时间: task.nextExecutionTime。
(4)判断当前系统时间和任务执行时间,来确定任务是否需要执行。
(5)使用任务状态标识taskFired=true任务需启动;否则,不启动。。
(6)任务操作完成,解除同步锁:synchronized(task.lock)。
2.2.7 解除同步锁:synchronized(queue)
2.2.8 执行任务:task.run()。
自定义的定时任务TimerTask,在此处就被执行。
3.案例
本例每隔60秒,获取一次UUID。
(1)实现TimerTask的run方法,把具体执行的业务任务写到run方法中。
(2)创建Timer对象,配置定时任务和传入TimerTask对象。
Timer提供多种任务调度策略,本例使用固定频率任务调度。
TimeMonitor,实现了InitializingBean接口,在afterPropertiesSet中初始化定时器。也就是TimeMonitor被容器初始化完成后,就会触发Timer定时器初始化,Timer定时器内部的任务线程会启动和任务队列会把使用者的定时任务添加到队列。Timer定时器就会生效运行。
@Slf4j @Service public class TimeMonitor implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { startTimerMonitor(); } private void startTimerMonitor() { TimerTask timerTask = new TimerTask() { @Override public void run() { log.info("定时任务执行开始."); String uuid = UUID.randomUUID().toString() .replace("-", "").toUpperCase(); log.info("执行业务,获取序列号,UUID = " + uuid); log.info("定时任务执行完成."); } }; // 定时器 Timer timer = new Timer(); // 延时时间 long delayTime = 6000L; // 执行频率 long period = 1000 * 60; timer.scheduleAtFixedRate(timerTask, delayTime, period); } }
4.测试
根据输出日志查看定时调度情况。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。