前段时间做一个springMVC项目,有一个功能是要定时拉取数据。做法就是启动一个定时器,定时这行。java有自带的定时器,不过在springMVC中不好集成,而且由于使用springMVC,很
多都是注解,写起来效率较高。当然quart定时器很好用,而且功能强大。自己想偷懒,找到spring 自带了一个轻量级的定时器spring schedule,使用注解一行代码就能实现。代码是这样的:
在spring 注解中加一行<task:annotation-driven />,代表支持这个定时器的注解使用,然后再定时器类中的方法上加一行@Scheduled(cron = "0 55 23 * * ?") 注解,当然这个类必须加
入spring组件里面。很容易,一个定时器就实现了,而且修改也很容易,变更cron表达式就好。
由于功能的增加,需要定时器的地方有好几个,自己什么也没想,把原先的方法照搬过来,也不会想到会出什么问题。那天发布项目测试的时候,突然发现该运行的内容没有,关键是日志也
没报错。这种问题不知道原因,都不好去解决。只能回到项目,一个一个打日志,调试。后面发现定时器也不是全部没启动,启动了一个。后面发现如果就只是一个定时器,运行没问题,难道这
个只支持一个定时器?我举得不太可能,写个代码测试一下,发现几个是没问题。后面觉得有可能这个定时器是阻塞的,就是同时只能执行一个任务,果不然,项目中的几个任务是需要一直运行
的,就是说要占几个线程。开始想去看sprng schedule的源码,感觉里面类之间牵涉太多了。就先看了java自带的定时器的源码,发现两个在那个问题的处理上很相似,看了一下
java.util.timer,它的核心执行代码,其实就是一个线程在一直轮询扫描各个定时器,既然是一个线程,那么肯定是阻塞的。下面做一下简要的分析。
核心有三个类,Timer,TimeTask ,其中Timer中又包括两个重要的类TaskQueue,TimerThread.类图如下(只介绍主体功能)
首先看TimerTask,VIRGIN,SCHEDULED,EXECUTED,CANCELLED分别代表执行的状态:未执行,执行中,已执行完,已取消。
方法中,run指该任务执行,cancel,取消该任务,scheduledExecutionTime返回下次执行任务时间。
Timer 其实是一个外观接口,提供定时任务的访问接口schedule
TaskQueue 其实是一个TimerTask的容器,属性中包含一个TimerTask数组。
方法则是对任务的管理
add,加入新的定时任务
getMin,获取时间最近的定时任务
removeMin,移除最近的定时任务
rescheduleMin,重新调度时间
fixUp 吧定时任务往前移,
fixDown 把定时任务往后移
总体来说,这个既是对定时任务的管理与调度。
定时的主要实现部分是在TimerThread
TimerThread实现Thread接口,也就是说通过这个线程来实现定时功能,
主要代码在mainLoop 代码如下
该函数已知扫描TaskQueue中的TimerTask任务,每次取时间最近的定时任务,如果时间没到,那么线程等待,如果判断轮到某个定时器执行,那么线程阻塞,直到该线程完成。由于轮询的是单一线程,所以在时间点上,每次只能有一个定时任务执行。这样,如果一个任务执行时间过长,那么其他的任务就得等待,如果一个任务一直在执行,那么其余的任务就永远执行不了,被阻塞了。这个和我用spring schedule相像。可能我用到的那个功能和java 自带的timer实现差不多,用到是单一线程的阻塞模式。
后边有时间逐步分析spring schedule,quart