网站发布流程,网站栏目建设,seo公司排名,网站建设彩铃传送门
分布式定时任务系列1#xff1a;XXL-job安装
分布式定时任务系列2#xff1a;XXL-job使用
分布式定时任务系列3#xff1a;任务执行引擎设计
分布式定时任务系列4#xff1a;任务执行引擎设计续
分布式定时任务系列5#xff1a;XXL-job中blockingQueue的应用 …传送门
分布式定时任务系列1XXL-job安装
分布式定时任务系列2XXL-job使用
分布式定时任务系列3任务执行引擎设计
分布式定时任务系列4任务执行引擎设计续
分布式定时任务系列5XXL-job中blockingQueue的应用
分布式定时任务系列6XXL-job触发日志过大引发的CPU告警
分布式定时任务系列7XXL-job源码分析之任务触发
分布式定时任务系列8XXL-job源码分析之远程调用 分布式定时任务系列9XXL-job路由策略
分布式定时任务系列10XXL-job源码分析之路由策略
番外篇从XXL-job路由策略的“服务容错“说起
Java并发编程实战1java中的阻塞队列
第一个问题XXL-job是如何做到定时触发的
不知道大家在看XXL-job源码的过程中有没有像我一样产生过一个疑惑那就是XXL-job到底是怎样做到让任务按时触发的呢或者说让任务定时定点如此听话 比如说一个邮件提醒功能每天晚上20:00点给相关的值班人员发邮件它到时间晚上20:00一定会触发吗会不会漏会不会判断不准超过时间21:00才触发
触发整体时序图
在分布式定时任务系列7XXL-job源码分析之任务触发节中从整体上梳理过任务触发的调用逻辑通过一个时序图来展现
上面图中圈起来的地方
立即创建一个线程Thread并启动不停扫描任务表xxl_job_info根据配置判断是否触发任务
对应的代码如下我把核心的地方贴出来了
{// 立即创建一个线程scheduleThread new Thread(new Runnable() {Overridepublic void run() {try {TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );} catch (InterruptedException e) {if (!scheduleThreadToStop) {logger.error(e.getMessage(), e);}}logger.info( init xxl-job admin scheduler success.);// pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps 1000/50 20)int preReadCount (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;// 不停扫描任务表xxl_job_info相当于线程的自旋while (!scheduleThreadToStop) {// Scan Joblong start System.currentTimeMillis();Connection conn null;Boolean connAutoCommit null;PreparedStatement preparedStatement null;boolean preReadSuc true;try {// JDBC操作数据库conn XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();connAutoCommit conn.getAutoCommit();conn.setAutoCommit(false);// 加上db悲观锁防止并发执行preparedStatement conn.prepareStatement( select * from xxl_job_lock where lock_name schedule_lock for update );preparedStatement.execute();// tx start// 1、pre readlong nowTime System.currentTimeMillis();// 查询所有任务列表一次最多6000个ListXxlJobInfo scheduleList XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime PRE_READ_MS, preReadCount);if (scheduleList!null scheduleList.size()0) {// 2、push time-ringfor (XxlJobInfo jobInfo: scheduleList) {// time-ring jumpif (nowTime jobInfo.getTriggerNextTime() PRE_READ_MS) {// 2.1、trigger-expire 5spass make next-trigger-timelogger.warn( xxl-job, schedule misfire, jobId jobInfo.getId());// 1、错过触发时间根据策略决定是否立即补尝执行一次MisfireStrategyEnum misfireStrategyEnum MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING);if (MisfireStrategyEnum.FIRE_ONCE_NOW misfireStrategyEnum) {// FIRE_ONCE_NOW 》 triggerJobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null);logger.debug( xxl-job, schedule push trigger : jobId jobInfo.getId() );}// 2、更新下次执行相关时间参数refreshNextValidTime(jobInfo, new Date());} else if (nowTime jobInfo.getTriggerNextTime()) {// 2.2、trigger-expire 5sdirect-trigger make next-trigger-time// 1、触发任务JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);logger.debug( xxl-job, schedule push trigger : jobId jobInfo.getId() );// 2、更新下次执行相关时间参数refreshNextValidTime(jobInfo, new Date());// next-trigger-time in 5s, pre-read againif (jobInfo.getTriggerStatus()1 nowTime PRE_READ_MS jobInfo.getTriggerNextTime()) {// 1、make ring secondint ringSecond (int)((jobInfo.getTriggerNextTime()/1000)%60);// 2、push time ringpushTimeRing(ringSecond, jobInfo.getId());// 3、fresh nextrefreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));}} else {// 2.3、trigger-pre-readtime-ring trigger make next-trigger-time// 1、make ring secondint ringSecond (int)((jobInfo.getTriggerNextTime()/1000)%60);// 2、push time ringpushTimeRing(ringSecond, jobInfo.getId());// 3、fresh nextrefreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));}}// 3、更新db表中下次执行相关时间参数for (XxlJobInfo jobInfo: scheduleList) {XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);}} else {preReadSuc false;}// tx stop} catch (Exception e) {if (!scheduleThreadToStop) {logger.error( xxl-job, JobScheduleHelper#scheduleThread error:{}, e);}} finally {// commitif (conn ! null) {try {conn.commit();} catch (SQLException e) {if (!scheduleThreadToStop) {logger.error(e.getMessage(), e);}}try {conn.setAutoCommit(connAutoCommit);} catch (SQLException e) {if (!scheduleThreadToStop) {logger.error(e.getMessage(), e);}}try {conn.close();} catch (SQLException e) {if (!scheduleThreadToStop) {logger.error(e.getMessage(), e);}}}// close PreparedStatementif (null ! preparedStatement) {try {preparedStatement.close();} catch (SQLException e) {if (!scheduleThreadToStop) {logger.error(e.getMessage(), e);}}}}long cost System.currentTimeMillis()-start;// Wait seconds, align secondif (cost 1000) { // scan-overtime, not waittry {// pre-read period: success scan each second; fail skip this period;TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000);} catch (InterruptedException e) {if (!scheduleThreadToStop) {logger.error(e.getMessage(), e);}}}}logger.info( xxl-job, JobScheduleHelper#scheduleThread stop);}});// 线程启动scheduleThread.setDaemon(true);scheduleThread.setName(xxl-job, admin JobScheduleHelper#scheduleThread);scheduleThread.start();}
这个JobScheduleHelper逻辑有多个分支但是里面核心是立即创建一个线程这里是单线程在run()方法中通过一个while循环来触发任务。但是这个while关键的一点是如果线程没有停止就会一直执行下去
// 不停扫描任务表xxl_job_info相当于线程的自旋while (!scheduleThreadToStop) {// 执行任务策略并触好,具体见上这里略过...
}
我们都知道Java里面的线程有可能阻塞或者由于CPU切换得不到时间片分配导致任务暂停这些情况下任务不就触发不了么所以XXL-job在run()方法里面写了类似死循环来尽量避免任务延迟触发这一点其实在其它定时任务设计中或框架也是一种通用思路所以这次会通过JDK自带的Timer工具类源码来印证、对比、扩展
JDK-Timer类源码解析
JDK-Timer什么是 JDK Timer 是Java开发工具包JDK中提供的一个定时器工具用于在后台线程中安排将来执行的任务。它可以安排任务为一次性执行或者以固定间隔重复执行。 Timer类自jdk1.3就引入作者是大名鼎鼎的Josh Bloch是google的首席JAVA架构师。既然它开始工作的早那么退休自然也早现在一般不再推荐使用 优点 简单易用适合简单的定时任务需求。线程安全多个线程可以共享一个Timer对象而无需外部同步。 缺点 单一Timer对象共享一个线程如果任务执行时间较长会影响后续任务的执行。不适合高并发或高性能要求的场景。 替代方案 ScheduledExecutorService 提供了更强大的功能包括线程池支持、更灵活的任务调度等。例如可以使用scheduleAtFixedRate和scheduleWithFixedDelay方法来安排任务的固定频率执行。 Quartz Scheduler 一个更强大的调度框架支持复杂的调度需求、持久化存储、集群等功能适合企业级应用。 但是通过它可以看看任务调度的原理以及学习一下大师的设计
JDK-Timer的使用
Timer的API方法并不多主要有以下几个
变量和类型方法描述voidcancel() 终止此计时器丢弃当前计划的任何任务。 intpurge() 从此计时器的任务队列中删除所有已取消的任务。 voidschedule(TimerTask task, long delay) 在指定的延迟后安排指定的任务执行。 voidschedule(TimerTask task, long delay, long period) 在指定 的延迟之后开始为重复的 固定延迟执行安排指定的任务。 voidschedule(TimerTask task, Date time) 计划在指定时间执行指定的任务。 voidschedule(TimerTask task, Date firstTime, long period) 从指定时间开始为重复的 固定延迟执行安排指定的任务。 voidscheduleAtFixedRate(TimerTask task, long delay, long period) 在指定的延迟之后开始为重复的 固定速率执行安排指定的任务。 voidscheduleAtFixedRate(TimerTask task, Date firstTime, long period) 从指定时间开始为重复的 固定速率执行安排指定的任务。
下面通过几个测试方法来感受一下
public class TimerTest {public static void main(String[] args) {// 通过new实例化一个timer实例Timer timer new Timer();// 只触发一次的任务:延迟1s执行(如果为0则表示立即执行)timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(name1 Thread.currentThread().getName() , time: new Date());}}, 1000L);// 固定频率执行的任务:延迟1s执行,每2秒执行一次TimerTask timerTask2 new TimerTask() {Overridepublic void run() {System.out.println(name2 Thread.currentThread().getName() , time: new Date());}};timer.schedule(timerTask2, 1000L, 2000L);// 固定频率执行的任务:延迟1s执行,每3秒执行一次;并指定首次执行的时间Date fixedDate getFixedDate();timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(name3 Thread.currentThread().getName() , time: new Date());}}, fixedDate, 3000L);}public static Date getFixedDate() {String dateString 2025-06-08 19:19:59;SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);try {Date specifiedDate sdf.parse(dateString);System.out.println(Specified Date: specifiedDate);return specifiedDate;} catch (Exception e) {System.out.println(Error parsing date: e.getMessage());}return null;}}
运行输出结果
Specified Date: Sun Jun 08 21:53:59 CST 2025
// name1输出在下面日志中可以看出只触发了一次属于一次性任务
name1Timer-0, time:Sun Jun 08 21:53:48 CST 2025
// name2输出在下面日志中可以每隔2s触发一次属于固定频率任务
name2Timer-0, time:Sun Jun 08 21:53:48 CST 2025
name2Timer-0, time:Sun Jun 08 21:53:50 CST 2025
name2Timer-0, time:Sun Jun 08 21:53:52 CST 2025
name2Timer-0, time:Sun Jun 08 21:53:54 CST 2025
name2Timer-0, time:Sun Jun 08 21:53:56 CST 2025
name2Timer-0, time:Sun Jun 08 21:53:58 CST 2025
// name3输出表示在指定的时间节点才触发了任务属于固定频率任务
name3Timer-0, time:Sun Jun 08 21:53:59 CST 2025
name2Timer-0, time:Sun Jun 08 21:54:00 CST 2025
name3Timer-0, time:Sun Jun 08 21:54:02 CST 2025
name2Timer-0, time:Sun Jun 08 21:54:02 CST 2025
name2Timer-0, time:Sun Jun 08 21:54:04 CST 2025
name3Timer-0, time:Sun Jun 08 21:54:05 CST 2025
除去上述几个API之外还有scheduleAtFixedRate相关的几个方法也是设置固定频率执行的操作区别是
scheduleAtFixedRate更注重任务执行频率如果任务由于其它原因被阻塞当恢复时会尽力去补偿执行遗漏的schedule这种更注重任务执行时间如果任务由于其它原因被阻塞当恢复时不会去补偿执行遗漏的
可以通过如下例子来感受
public static void main(String[] args) {// 通过new实例化一个timer实例Timer timer new Timer();// 只触发一次的任务:延迟1s执行(如果为0则表示立即执行)timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(name1 Thread.currentThread().getName() , time: new Date());try {// 模拟任务阻塞线程睡眠6sThread.sleep(6000, TimeUnit.SECONDS.ordinal());} catch (InterruptedException e) {throw new RuntimeException(e);}}}, 1000L);timer.scheduleAtFixedRate(new TimerTask() {Overridepublic void run() {System.out.println(name4 Thread.currentThread().getName() , time: new Date());}}, 1000L, 2000L);}// name1输出在下面日志中可以看出只触发了一次属于一次性任务
name1Timer-0, time:Sun Jun 08 22:31:18 CST 2025
// name4输出在下面几个name4的连续输出中可以看到是同一时刻执行的这就是由于任务1中线程阻塞了6s所以被补偿执行了
name4Timer-0, time:Sun Jun 08 22:31:24 CST 2025
name4Timer-0, time:Sun Jun 08 22:31:24 CST 2025
name4Timer-0, time:Sun Jun 08 22:31:24 CST 2025
name4Timer-0, time:Sun Jun 08 22:31:24 CST 2025
// name4输出后面都是每隔2s的正常输出了
name4Timer-0, time:Sun Jun 08 22:31:26 CST 2025
name4Timer-0, time:Sun Jun 08 22:31:28 CST 2025
name4Timer-0, time:Sun Jun 08 22:31:30 CST 2025
JDK-Timer的源码
从构造方法看类结构
先看下Timer的创建
// 通过new实例化一个timer实例Timer timer new Timer();
跟进Timer的类的源码看看这个构造方法
/*** Creates a new timer. The associated thread does inot/i* {linkplain Thread#setDaemon run as a daemon}.*/public Timer() {this(Timer- serialNumber());}/*** Creates a new timer whose associated thread has the specified name.* The associated thread does inot/i* {linkplain Thread#setDaemon run as a daemon}.** param name the name of the associated thread* throws NullPointerException if {code name} is null* since 1.5*/public Timer(String name) {thread.setName(name);thread.start();}
首先Timer()无参构造方法调用会继续调用Timer(String name)这个有参构造传递一个线程的名称名称为Timer-序列号
/*** This ID is used to generate thread names.*/private final static AtomicInteger nextSerialNumber new AtomicInteger(0);// 上面的原子类注释说明了nextSerialNumber是生成一个线程名称private static int serialNumber() {return nextSerialNumber.getAndIncrement();}
那么这个线程到底是什么呢
TimerThread-定时器线程
我们在前面说过Timer是单线程的其实就是在这里绑定的继续看下thread定义 /*** The timer task queue. This data structure is shared with the timer* thread. The timer produces tasks, via its various schedule calls,* and the timer thread consumes, executing timer tasks as appropriate,* and removing them from the queue when theyre obsolete.*/private final TaskQueue queue new TaskQueue();/*** The timer thread.*/private final TimerThread thread new TimerThread(queue); 这里创建了一个TimerThread对象得到一个实例线程
class TimerThread extends Thread {/*** This flag is set to false by the reaper to inform us that there* are no more live references to our Timer object. Once this flag* is true and there are no more tasks in our queue, there is no* work left for us to do, so we terminate gracefully. Note that* this field is protected by queues monitor!*/boolean newTasksMayBeScheduled true;/*** Our Timers queue. We store this reference in preference to* a reference to the Timer so the reference graph remains acyclic.* Otherwise, the Timer would never be garbage-collected and this* thread would never go away.*/private TaskQueue queue;TimerThread(TaskQueue queue) {this.queue queue;}public void run() {try {mainLoop();} finally {// Someone killed this Thread, behave as if Timer cancelledsynchronized(queue) {newTasksMayBeScheduled false;queue.clear(); // Eliminate obsolete references}}}/*** The main timer loop. (See class comment.)*/private void mainLoop() {// 实现暂时略后面会详细讨论}
}
这里创建了一个TimerThread对象得到一个实例线程 在学习Java并发编程的时候肯定学习过线程创建的2种方式继承Thread或者实现Runnable接口这里就是典型的继续Thread类
TaskQueue-定时器任务队列
除此以外在创建Timer实例的时候还实例化了一个队列
// 定时器任务队列。这一数据结构与定时器线程共享。
//定时器通过不同的调度调用生成任务定时器线程则负责消费这些任务在适当时机执行它们并在任务过期后从队列中移除。
private final TaskQueue queue new TaskQueue(); 从上面可以得到它的主要作用
数据结构共享队列由定时器和定时器线程共同操作。生产者-消费者模型定时器生产者创建任务定时器线程消费者处理任务。任务生命周期执行后或过期时任务会被移出队列。
继续跟进定时器任务队列的类定义
class TaskQueue {// 任务数组是一个最少堆结构private TimerTask[] queue new TimerTask[128];
}
里面定义了一个TimerTask数组定义了定时任务
TimerTask-定时任务
// 定义了一个抽象类实现了Runnable接口
public abstract class TimerTask implements Runnable {// 定义了一个抽象方法定时任务要执行业务方法就是通过实现这个方法public abstract void run();// 其余参数先略过...
} 至此Timer的相关类已经都出现了可以据此得到它的类图
类图 至此得到了Timer类的完整类图其中
Timer是定时器主类持有一个执行线程TimerThread和一个任务队列TaskQueue TimerThread类负责执行定时任务引用了任务队列TaskQueueTaskQueue类为任务队列里面持有一个TimerTask数组也是最小堆结构TimerTask为业务要实现的定时任务接口抽象类它实现Runable接口所以自然要求实现对应的run()方法
接下来就仔细分析一下定时任务的整个管理、执行流程
定时器-任务线程启动
再回到刚才的构造方法来 public Timer(String name) {thread.setName(name);// 线程启动thread.start();} 只要创建了一个Timer实例new Timer()就会立即启动执行线程thread.start
定时任务执行
线程启动之后会立即执行线程的run方法
public void run() {try {// 主循环所有的定时任务都是在这里触发执行的mainLoop();} finally {// Someone killed this Thread, behave as if Timer cancelledsynchronized(queue) {newTasksMayBeScheduled false;queue.clear(); // Eliminate obsolete references}}} 从方法命名上就可以看出这个mainLoop是一个循环方法继续跟进它
private void mainLoop() {// 死循环while (true) {try {// 当前需要触发的定时任务TimerTask task;// 触发标志位boolean taskFired;// 直接同步任务队列防止并发synchronized(queue) {// Wait for queue to become non-empty// 如果队列为空且Timer未取消则进行等待关于Timer取消造成newTasksMayBeScheduled为false的放到后面讨论while (queue.isEmpty() newTasksMayBeScheduled)queue.wait();// 如果队列为空且Timer取消主动或被动则跳出循环注意只有这种情况才会真正结束死循环其实就是newTasksMayBeScheduled为false了if (queue.isEmpty())break; // Queue is empty and will forever remain; die// Queue nonempty; look at first evt and do the right thinglong 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;// 判断任务是否需要触发触发条件是执行时间在当前时间之前即executionTimecurrentTimeif (taskFired (executionTimecurrentTime)) {// 如果是一次性任务if (task.period 0) { // Non-repeating, remove// 则从任务队列中删除任务queue.removeMin();// 修改任务的状态为已执行task.state TimerTask.EXECUTED;// 如果是周期性任务} else { // Repeating task, reschedule// 则重新计算下一次触发时间queue.rescheduleMin(task.period0 ? currentTime - task.period: executionTime task.period);}}}// 如果任务未触发则继续等待时间executionTime - currentTimeif (!taskFired) // Task hasnt yet fired; waitqueue.wait(executionTime - currentTime);}// 如果任务触发则调用run()方法执行任务if (taskFired) // Task fired; run it, holding no lockstask.run();} catch(InterruptedException e) {}}} 从第一行代码就看到了我们今天引出的问题为什么定时任务的触发是死循环看来不止是XXL-job这样实现的JDK-Timer也是这样实现的
while (true) {
}
关于这个问题网上AI给出的一种解释 任务调度原理采用死循环设计的原因在于其能够有效地管理和执行多任务提高系统的整体效率和响应速度。在任务调度中每个任务通常设计为一个死循环这是因为死循环结构能够确保任务持续运行并等待事件或条件的发生从而实现对任务的持续监控和执行。 死循环在任务调度中的作用 持续运行死循环确保任务能够持续运行不断检查和处理事件或条件。这对于实时响应和任务管理至关重要。事件驱动死循环结构使得任务能够响应各种事件或信号如定时器中断、外部事件等。当特定事件发生时任务可以执行相应的操作。资源分配通过维护就绪队列和数据结构调度程序能够动态选择待执行任务实现处理器资源的有效分配和管理 定时任务添加
关于定时任务执行主流程源码的讨论先点到为止。回过头来再看看定时器添加任务的例子
// 只触发一次的任务:延迟1s执行(如果为0则表示立即执行)timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(name1 Thread.currentThread().getName() , time: new Date());}}, 1000L);
继续跟进schedule方法
public void schedule(TimerTask task, long delay) {// 延迟时间要0意思就是任务只能当前时间之后这点很容易理解if (delay 0)throw new IllegalArgumentException(Negative delay.);// 从这里就可以看出delay0原因因为执行时间直接取的是 当前时间延迟时间sched(task, System.currentTimeMillis()delay, 0);}private void sched(TimerTask task, long time, long period) {// 任务执行时间if (time 0)throw new IllegalArgumentException(Illegal execution time.);// Constrain value of period sufficiently to prevent numeric// overflow while still being effectively infinitely large.if (Math.abs(period) (Long.MAX_VALUE 1))period 1;// 同步队列synchronized(queue) {// 如果定时器被取消直接抛出异常if (!thread.newTasksMayBeScheduled)throw new IllegalStateException(Timer already cancelled.);// 同步任务synchronized(task.lock) {if (task.state ! TimerTask.VIRGIN)throw new IllegalStateException(Task already scheduled or cancelled);// 更新任务信息设置下次执行时间、周期、任务状态为SCHEDULED在这个状态的任务才能被执行这个可以在上面执行器中代码可以印证task.nextExecutionTime time;task.period period;task.state TimerTask.SCHEDULED;}// 添加到任务队列中queue.add(task);// 如果任务恰好是队列第一个元素则直接通知任务队列执行任务if (queue.getMin() task)// 这个代码还有另外一个作用就是唤醒被阻塞的队列因为在执行的代码中如果队列为空会进行等待的。 queue.notify();}}
任务执行流程图
结合上面的任务执行/添加源码分析在不考虑主动取消定时器的情况可以大致得出下面的任务执行流程图 生产者-消费者模型
至此梳理了任务执行的流程留意一下可以看到里面用到了wait()notify()方法用来做线程的阻塞与唤醒这其实是典型的生产者-消费者模型
生产者
业务类通过调用Timer的schedule方法添加任务TimerTask其实就是生产者比如上面的TimerTest
消费者
这里的执行线程TimerThread其实就是消费者它通过while循环不停的从任务队列中取出定时任务
但是这里要明确一下执行线程既是消费者也是生产者!
可以从mainLoop()的方法可以体现
if (taskFired (executionTimecurrentTime)) {// 如果是一次性任务if (task.period 0) { // Non-repeating, remove// 则从任务队列中删除任务queue.removeMin();// 修改任务的状态为已执行task.state TimerTask.EXECUTED;// 如果是周期性任务} else { // Repeating task, reschedule// 则重新计算下一次触发时间queue.rescheduleMin(task.period0 ? currentTime - task.period: executionTime task.period);}}
如果任务的周期字段是period如果是0表示一次性任务如果period0则表示周期性任务在任务触发时同时计算下一次触发时间就是queue.rescheduleMin方法
void rescheduleMin(long newTime) {// 更新下次执行时间当前时间周期queue[1].nextExecutionTime newTime;// 调整堆顺序保持最小堆特性fixDown(1);}
通过这个方法其实相当于又往队列了放了一个元素就是同一个任务还在队列中只是下次执行时间nextExecutionTime被更新了这样在任务触发之后可以继续被执行
关于复用wait和notify实现线程间的协作这里不展开。只是列出一下相关的API
voidnotify() 唤醒正在此对象监视器上等待的单个线程。 voidnotifyAll() 唤醒等待此对象监视器的所有线程。
voidwait() 导致当前线程等待它被唤醒通常是 通知或 中断 。 voidwait(long timeoutMillis) 导致当前线程等待它被唤醒通常是 通知或 中断 或者直到经过一定量的实时。 voidwait(long timeoutMillis, int nanos) 导致当前线程等待它被唤醒通常是 通知或 中断 或者直到经过一定量的实时。 最小堆
前面提到过 TaskQueue类为任务队列里面持有一个TimerTask数组也是最小堆结构 关于最小堆的介绍在这里列出来 经过排序的完全二叉树 最小堆是一种经过排序的完全二叉树其中任一非终端节点的数据值均不大于其左子结点和右子结点的值。 这里在添加任务的时候其实就进行堆排序
void add(TimerTask task) {// Grow backing store if necessaryif (size 1 queue.length)queue Arrays.copyOf(queue, 2*queue.length);queue[size] task;fixUp(size);}private void fixUp(int k) {while (k 1) {int j k 1;if (queue[j].nextExecutionTime queue[k].nextExecutionTime)break;TimerTask tmp queue[j]; queue[j] queue[k]; queue[k] tmp;k j;}} 附录Java创建的2种方式 1. 继承 Thread 类 这是最传统的创建线程的方式。通过创建一个新的类继承自 java.lang.Thread 类并重写 run() 方法来定义线程的执行体。然后创建该类的实例并调用其 start() 方法来启动线程。 2. 实现 Runnable 接口 这种方式更加灵活因为它允许你将线程的执行代码封装在一个实现了 Runnable 接口的类的实例中。这种方式更适合资源共享和便于线程的共享和管理。