java.util.Timer 定时问题
相信大家都很熟悉java.util.Timer类,java类库中提供的简单的执行定时任务的类,使用也非常简单。自定义任务扩展抽象类TimeTask,实现抽象方法void run(),之后使用Timer对象的schedule( TimerTask task,long delay,long period )方法即可。
直观的观察此函数,意思是让任务延迟dealy 毫秒之后以period为周期执行。可是我们的需求一般是让某个任务整点或者一天的某个时间执行,那么该怎么做呢?
TimeTask任务整点执行
其实很简单,关键在于delay参数,我们可以借助 Calendar 间接实现该功能。比如某个任务需要整点执行。我们只需要设置delay大小为 程序启动时间所在小时的最后一秒减去当前时间。然后让任务以3600*1000的period执行。下面是计算当前时间未来的第一个整点。
Data day = new Date();
cal.setTime(day);
// cal.set(Calendar.HOUR_OF_DAY, cal.getMaximum(Calendar.HOUR_OF_DAY));
cal.set(Calendar.MINUTE,cal.getMaximum(Calendar.MINUTE));
cal.set(Calendar.SECOND,cal.getMaximum(Calendar.SECOND));
cal.set(Calendar.MILLISECOND,cal.getMaximum(Calendar.MILLISECOND));
return cal.getTime();
当然使用schedule(TimerTask task, Date firstTime, long period)更方便。
每天某个时间执行和每月,甚至每年也可以以此类推。
Timer的实现方式
Timer 的设计核心是一个 TaskQueue 和一个 TaskThread。
Timer 将接收到的任务丢到自己的 TaskQueue中,TaskQueue按照 Task 的最初执行时间进行排序。
TimerThread 在创建 Timer 时会启动成为一个守护线程。这个线程会轮询所有任务,找到一个最近要执行的任务,然后休眠,当到达最近要执行任务的开始时间点,TimerThread 被唤醒并执行该任务。之后 TimerThread 更新最近一个要执行的任务,继续休眠。
Timer使用中遇到的问题
某个定时任务整点统计上一小时内的某项数据,运行5天后发现,统计时间变成的xx:00:59秒。通过分析发现,因为这个统计很复杂,每次统计要花费大概0.5秒,这样一天下来就用12秒钟的消耗。这样执行的话,大概150天的时间,此任务执行共需要1800秒。这些统计的数据就会变为某个小时:30分到下一小时30分的统计数据..
这显然偏离了程序的目的,为什么会这样呢?
问题在:
schedule(TimerTask task, Date firstTime, long period)是用重复固定延迟触发的,每次执行之后每次执行时间为上一次任务结束起向后推一个时间间隔,
即每次执行时间为:
执行第一次开始的时间:initialDelay,
执行第二次开始的时间 :initialDelay+executeTime+period,
执行第三次开始的时间 :initialDelay+2*executeTime+2* period。
解决办法是使用另外一个函数:
scheduleAtFixedRate(TimerTask task, long delay,long period) 安排指定的任务在指定的延迟后开始进行重复的固定速率执行。
于是便引发出了触发器类型的概念。
Timer触发器类型
一次性触发器(One-off)
重复固定延迟触发器(Fixed-Delay)
重复定时触发器(Fixed-Rated)
说明:
一次性触发器只能执行一次,执行完成后,不能被再度重新使用;
下面举例说明固定延迟触发器和定时触发器区别:
假如17:00开始任务执行,任务执行时间为30分钟,每小时执行一次,第一次运行将于17:30结束。
如果采用固定延迟触发器,第二次运行将在 18:30开始,计算方法为前一次结束时间加上间隔时间;
如果采用定时触发器,第二次运行将在18:00开始,计算方法为前一次开始时间加上间隔时间。
Timer的局限性
Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。如果说某个任务出现了异常,这个定时器上的所有任务都会终止。
ScheduledExecutor
鉴于 Timer 的上述缺陷,Java 5 推出了基于线程池设计的 ScheduledExecutor。
其设计思想是,每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。
需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
long initialDelay1 = 1;
long period1 = 1;
// 从现在开始1秒钟之后,每隔1秒钟执行一次job1
service.scheduleAtFixedRate(new ScheduledExecutorTest("job1"), initialDelay1, period1, TimeUnit.SECONDS);
Quartz 与 JCronTab
另外两种方式是使用开源类库Quartz 与 JCronTab,如何实现请参看:
具体实现见:https://www.ibm.com/developerworks/cn/java/j-lo-taskschedule/
Java常用的对任务进行调度的实现方法,即 Timer,ScheduledExecutor, Quartz 以及 JCronTab。对于简单的基于起始时间点与时间间隔的任务调度,使用 Timer 就足够了;如果需要同时调度多个任务,基于线程池的ScheduledTimer 是更为合适的选择;当任务调度的策略复杂到难以凭借起始时间点与时间间隔来描述时,Quartz与 JCronTab 则体现出它们的优势。熟悉Unix/Linux 的开发人员更倾向于 JCronTab,且JCronTab 更适合与 Web 应用服务器相结合。Quartz的 Trigger 与 Job 松耦合设计使其更适用于 Job 与 Trigger 的多对多应用场景。
http://blog.csdn.net/xiaojianpitt/article/details/7659422
一:简单说明
ScheduleExecutorService接口中有四个重要的方法,其中scheduleAtFixedRate和scheduleWithFixedDelay在实现定时程序时比较方便。
下面是该接口的原型定义
java.util.concurrent.ScheduleExecutorService extends ExecutorService extends Executor
接口scheduleAtFixedRate原型定义及参数说明
[java] view plain copy
- public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
- long initialDelay,
- long period,
- TimeUnit unit);
command:执行线程
initialDelay:初始化延时
period:两次开始执行最小间隔时间
unit:计时单位
接口scheduleWithFixedDelay原型定义及参数说明
[java] view plain copy
- public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
- long initialDelay,
- long delay,
- TimeUnit unit);
command:执行线程
initialDelay:初始化延时
period:前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)
unit:计时单位
二:功能示例
1.按指定频率周期执行某个任务。
初始化延迟0ms开始执行,每隔100ms重新执行一次任务。
[java] view plain copy
- /**
- * 以固定周期频率执行任务
- */
- public static void executeFixedRate() {
- ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
- executor.scheduleAtFixedRate(
- new EchoServer(),
- 0,
- 100,
- TimeUnit.MILLISECONDS);
- }
间隔指的是连续两次任务开始执行的间隔。
2.按指定频率间隔执行某个任务。
初始化时延时0ms开始执行,本次执行结束后延迟100ms开始下次执行。
[java] view plain copy
- /**
- * 以固定延迟时间进行执行
- * 本次任务执行完成后,需要延迟设定的延迟时间,才会执行新的任务
- */
- public static void executeFixedDelay() {
- ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
- executor.scheduleWithFixedDelay(
- new EchoServer(),
- 0,
- 100,
- TimeUnit.MILLISECONDS);
- }
3.周期定时执行某个任务。
有时候我们希望一个任务被安排在凌晨3点(访问较少时)周期性的执行一个比较耗费资源的任务,可以使用下面方法设定每天在固定时间执行一次任务。
[java] view plain copy
- /**
- * 每天晚上8点执行一次
- * 每天定时安排任务进行执行
- */
- public static void executeEightAtNightPerDay() {
- ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
- long oneDay = 24 * 60 * 60 * 1000;
- long initDelay = getTimeMillis("20:00:00") - System.currentTimeMillis();
- initDelay = initDelay > 0 ? initDelay : oneDay + initDelay;
- executor.scheduleAtFixedRate(
- new EchoServer(),
- initDelay,
- oneDay,
- TimeUnit.MILLISECONDS);
- }
[java] view plain copy
- /**
- * 获取指定时间对应的毫秒数
- * @param time "HH:mm:ss"
- * @return
- */
- private static long getTimeMillis(String time) {
- try {
- DateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
- DateFormat dayFormat = new SimpleDateFormat("yy-MM-dd");
- Date curDate = dateFormat.parse(dayFormat.format(new Date()) + " " + time);
- return curDate.getTime();
- } catch (ParseException e) {
- e.printStackTrace();
- }
- return 0;
- }
4.辅助代码
[java] view plain copy
- class EchoServer implements Runnable {
- @Override
- public void run() {
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("This is a echo server. The current time is " +
- System.currentTimeMillis() + ".");
- }
- }
三:一些问题
上面写的内容有不严谨的地方,比如对于scheduleAtFixedRate方法,当我们要执行的任务大于我们指定的执行间隔时会怎么样呢?
对于中文API中的注释,我们可能会被忽悠,认为无论怎么样,它都会按照我们指定的间隔进行执行,其实当执行任务的时间大于我们指定的间隔时间时,它并不会在指定间隔时开辟一个新的线程并发执行这个任务。而是等待该线程执行完毕。
源码注释如下:
[java] view plain copy
- * Creates and executes a periodic action that becomes enabled first
- * after the given initial delay, and subsequently with the given
- * period; that is executions will commence after
- * <tt>initialDelay</tt> then <tt>initialDelay+period</tt>, then
- * <tt>initialDelay + 2 * period</tt>, and so on.
- * If any execution of the task
- * encounters an exception, subsequent executions are suppressed.
- * Otherwise, the task will only terminate via cancellation or
- * termination of the executor. If any execution of this task
- * takes longer than its period, then subsequent executions
- * may start late, but will not concurrently execute.
根据注释中的内容,我们需要注意的时,我们需要捕获最上层的异常,防止出现异常中止执行,导致周期性的任务不再执行。
四:除了我们自己实现定时任务之外,我们可以使用Spring帮我们完成这样的事情。
Spring自动定时任务配置方法(我们要执行任务的类名为com.study.MyTimedTask)
[html] view plain copy
- <bean id="myTimedTask" class="com.study.MyTimedTask"/>
[html] view plain copy
- <bean id="doMyTimedTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
- <property name="targetObject" ref="myTimedTask"/>
- <property name="targetMethod" value="execute"/>
- <property name="concurrent" value="false"/>
- </bean>
[html] view plain copy
- <bean id="myTimedTaskTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
- <property name="jobDetail" ref="doMyTimedTask"/>
- <property name="cronExpression" value="0 0 2 * ?"/>
- </bean>
[html] view plain copy
- <bean id="doScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
- <property name="triggers">
- <list>
- <ref local="myTimedTaskTrigger"/>
- </list>
- </property>
- </bean>
[html] view plain copy
- <bean id="doScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
- <property name="triggers">
- <list>
- <bean class="org.springframework.scheduling.quartz.CronTriggerBean">
- <property name="jobDetail"/>
- <bean id="doMyTimedTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
- <property name="targetObject">
- <bean class="com.study.MyTimedTask"/>
- </property>
- <property name="targetMethod" value="execute"/>
- <property name="concurrent" value="false"/>
- </bean>
- </property>
- <property name="cronExpression" value="0 0 2 * ?"/>
- </bean>
- </list>
- </property>
- </bean>
http://blog.csdn.net/tsyj810883979/article/details/8481621