定时问题

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

  1. public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
  2. long initialDelay,
  3. long period,
  4. TimeUnit unit);

command:执行线程
initialDelay:初始化延时
period:两次开始执行最小间隔时间
unit:计时单位

接口scheduleWithFixedDelay原型定义及参数说明

[java] view plain copy

  1. public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
  2. long initialDelay,
  3. long delay,
  4. TimeUnit unit);

command:执行线程
initialDelay:初始化延时
period:前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)
unit:计时单位

二:功能示例

1.按指定频率周期执行某个任务。

初始化延迟0ms开始执行,每隔100ms重新执行一次任务。

[java] view plain copy

  1. /**
  2. * 以固定周期频率执行任务
  3. */
  4. public static void executeFixedRate() {
  5. ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
  6. executor.scheduleAtFixedRate(
  7. new EchoServer(),
  8. 0,
  9. 100,
  10. TimeUnit.MILLISECONDS);
  11. }

间隔指的是连续两次任务开始执行的间隔。

2.按指定频率间隔执行某个任务。

初始化时延时0ms开始执行,本次执行结束后延迟100ms开始下次执行。

[java] view plain copy

  1. /**
  2. * 以固定延迟时间进行执行
  3. * 本次任务执行完成后,需要延迟设定的延迟时间,才会执行新的任务
  4. */
  5. public static void executeFixedDelay() {
  6. ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
  7. executor.scheduleWithFixedDelay(
  8. new EchoServer(),
  9. 0,
  10. 100,
  11. TimeUnit.MILLISECONDS);
  12. }

3.周期定时执行某个任务。

有时候我们希望一个任务被安排在凌晨3点(访问较少时)周期性的执行一个比较耗费资源的任务,可以使用下面方法设定每天在固定时间执行一次任务。

[java] view plain copy

  1. /**
  2. * 每天晚上8点执行一次
  3. * 每天定时安排任务进行执行
  4. */
  5. public static void executeEightAtNightPerDay() {
  6. ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
  7. long oneDay = 24 * 60 * 60 * 1000;
  8. long initDelay  = getTimeMillis("20:00:00") - System.currentTimeMillis();
  9. initDelay = initDelay > 0 ? initDelay : oneDay + initDelay;
  10. executor.scheduleAtFixedRate(
  11. new EchoServer(),
  12. initDelay,
  13. oneDay,
  14. TimeUnit.MILLISECONDS);
  15. }

[java] view plain copy

  1. /**
  2. * 获取指定时间对应的毫秒数
  3. * @param time "HH:mm:ss"
  4. * @return
  5. */
  6. private static long getTimeMillis(String time) {
  7. try {
  8. DateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
  9. DateFormat dayFormat = new SimpleDateFormat("yy-MM-dd");
  10. Date curDate = dateFormat.parse(dayFormat.format(new Date()) + " " + time);
  11. return curDate.getTime();
  12. } catch (ParseException e) {
  13. e.printStackTrace();
  14. }
  15. return 0;
  16. }

4.辅助代码

[java] view plain copy

  1. class EchoServer implements Runnable {
  2. @Override
  3. public void run() {
  4. try {
  5. Thread.sleep(50);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. System.out.println("This is a echo server. The current time is " +
  10. System.currentTimeMillis() + ".");
  11. }
  12. }

三:一些问题

上面写的内容有不严谨的地方,比如对于scheduleAtFixedRate方法,当我们要执行的任务大于我们指定的执行间隔时会怎么样呢?

对于中文API中的注释,我们可能会被忽悠,认为无论怎么样,它都会按照我们指定的间隔进行执行,其实当执行任务的时间大于我们指定的间隔时间时,它并不会在指定间隔时开辟一个新的线程并发执行这个任务。而是等待该线程执行完毕。

源码注释如下:

[java] view plain copy

  1. * Creates and executes a periodic action that becomes enabled first
  2. * after the given initial delay, and subsequently with the given
  3. * period; that is executions will commence after
  4. * <tt>initialDelay</tt> then <tt>initialDelay+period</tt>, then
  5. * <tt>initialDelay + 2 * period</tt>, and so on.
  6. * If any execution of the task
  7. * encounters an exception, subsequent executions are suppressed.
  8. * Otherwise, the task will only terminate via cancellation or
  9. * termination of the executor.  If any execution of this task
  10. * takes longer than its period, then subsequent executions
  11. * may start late, but will not concurrently execute.

根据注释中的内容,我们需要注意的时,我们需要捕获最上层的异常,防止出现异常中止执行,导致周期性的任务不再执行。

四:除了我们自己实现定时任务之外,我们可以使用Spring帮我们完成这样的事情。

Spring自动定时任务配置方法(我们要执行任务的类名为com.study.MyTimedTask)

[html] view plain copy

  1. <bean id="myTimedTask" class="com.study.MyTimedTask"/>

[html] view plain copy

  1. <bean id="doMyTimedTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
  2. <property name="targetObject" ref="myTimedTask"/>
  3. <property name="targetMethod" value="execute"/>
  4. <property name="concurrent" value="false"/>
  5. </bean>

[html] view plain copy

  1. <bean id="myTimedTaskTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
  2. <property name="jobDetail" ref="doMyTimedTask"/>
  3. <property name="cronExpression" value="0 0 2 * ?"/>
  4. </bean>

[html] view plain copy

  1. <bean id="doScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  2. <property name="triggers">
  3. <list>
  4. <ref local="myTimedTaskTrigger"/>
  5. </list>
  6. </property>
  7. </bean>

[html] view plain copy

    1. <bean id="doScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    2. <property name="triggers">
    3. <list>
    4. <bean class="org.springframework.scheduling.quartz.CronTriggerBean">
    5. <property name="jobDetail"/>
    6. <bean id="doMyTimedTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    7. <property name="targetObject">
    8. <bean class="com.study.MyTimedTask"/>
    9. </property>
    10. <property name="targetMethod" value="execute"/>
    11. <property name="concurrent" value="false"/>
    12. </bean>
    13. </property>
    14. <property name="cronExpression" value="0 0 2 * ?"/>
    15. </bean>
    16. </list>
    17. </property>
    18. </bean>

http://blog.csdn.net/tsyj810883979/article/details/8481621

时间: 2024-10-13 11:32:18

定时问题的相关文章

SqlServer定时备份数据库和定时杀死数据库死锁解决

PS:Sqlserver 2008 R2,windows 8 64位 1.备份数据库 因为要备份,我们就要用到Sqlserver的代理,默认数据库的代理是不开启的.需要我们手动开启的. 执行备份数据库脚本,现在将脚本公布,其实将这一段代码中需要保存的文件路径和数据库名称替换一下就可以实现备份了.但是还没有达到定时备份的目的 ? 1 2 3 4 5 6 7 8 9 10 11 --自动备份并保存最近5天的SQL数据库作业脚本 宋彪 20130310 DECLARE @filename VARCHA

iOS: 零误差或极小误差的定时执行或延迟执行?

问题如下: 节奏类游戏需要执行很多的跟音乐节拍相关的操作,并且为了保证节奏感,需要让操作跟节拍的关系十分紧密.对两者间隔要求不能超过0.02秒或更低. 目前使用了 GCD 中的 asyncAfter(deadline:)方法,不过误差总是要大于0.05秒,并且还无法保证误差会不会传递下去.请问有更好的方式来解决误差吗? var time = Date().timeIntervalSince1970 let dq = DispatchQueue(label: "queue", qos:

MYSQL 定时自动执行EVENT

MySQL从5.1开始支持EVENT功能,类似Oracle和MSSQL的定时任务job功能.有了这个功能之后我们就可以让MySQL自动的执行存储过程来实现数据汇总等功能了,不用像以前哪样手动操作完成了.下面我们来测试下,在MYSQL中如何自动执行指定存储过程,实现相关功能. 一.创建测试表CREATE TABLE EVENT_table(id INT auto_increment PRIMARY KEY NOT NULL,conent VARCHAR(80))ENGINE=INNODB DEFA

使用Navicat定时备份mysql数据库和创建报表并邮件自动发送

数据库备份在现代计算机高速发展的今日变得日益重要,程序员往往因为不重视而忽略备份数据,导致数据丢失,造成非常严重的后果.定时备份无疑是解决备份的最好的途径,本文主要使用Navicat来自动备份数据库和创建相关的计划任务. (一)使用Navicat自动备份数据库: 1,打开navicat客户端,连上mysql后,双击左边你想要备份的数据库.点击"计划",再点击"新建批处理作业"具体如下图. 2,双击上面的可用任务,它就会到下面的列表里去,也代表你选择了这个任务 3,点

定时开关机方案

WOL(从网卡唤醒)诸多限制,内网都不稳定,外网更不用说,放弃 断电恢复上次状态,必须的 通电即开机,必须的 WIFI智能开关一个 ? 受管理的服务器 Esxi 6 2台,windows 2012 2台 统一使用WIFI开关,esxi6设置SSH自启动,设置VM随主机开关机,HYPERV设置VM自启动 设置windows的定时任务,shutdown 使用C# SHARPSSH编写一个小程序,通过SSH连接上esxi执行/sbin/poweroff指令,设置windows定时任务,定时执行该程序使

linux 定时自动异地备份

目的:每天将A服务器(192.168.0.23)上的文件复制到B服务器(192.168.0.25)上,如果已经存在则不复制. 思路: 首先统一服务器时间,避免出现莫名的错误或者管理上的混乱.用NTP 由于是跨服务器复制,涉及到密码自动录入的问题.需要解决. 用scp复制,会覆盖已经存在的文件,增加无用的工作.改用rsync. 由于要定时自动执行命令,需要用到crontab. 解决步骤: 解决时间同步问题 用ntpdate 192.168.0.253将内部NTP服务器的时间同步过来.为了以后的自动

Rsync + Crontab实现定时文件同步(首次全量+后续增量)

一.简介 在配置HA的时候,需要配置多个节点的配置,节点间的配置很多时候都是类似重复的,也许可以通过scp等手段实现,但是每次的更改都需要手动scp也显得麻烦,这边我们可以通过rsync的方式来实现文件的同步. 使用rsync+crontab做定时同步时,主服务器端开启rsync守护进程,而镜像服务器是运行rsync客户端,平时一般会利用crontab定时获取rsync服务器上的数据. 二.配置 ①rsync的服务端(文件发送出去端) 以守护进程的形式后台运行 1.rsync的安装和文件包组成

20150218【改进Poll定时查询】IMX257实现GPIO-IRQ中断按键获取键值驱动程序

[改进Poll定时查询]IMX257实现GPIO-IRQ中断按键获取键值驱动程序 2015-02-18 李海沿 按键驱动程序中,如果不使用read函数中使程序休眠的,而是还是使用查询方式的话,可以使用Poll函数,来控制一定时间内,如果有按键发生,则立即返回键值. 同时,poll也可以同时监控多个(比如说按键,鼠标,等)一旦发生事件则立即返回. 我们在linux查看帮助: 从帮助中的说明得知, poll, ppoll - wait for some event on a file descrip

GS(道具,帮会)定时存储

//最近数据库存储做了重大改变,数据库内部的回头再说,先看看GS这边的 1.现在感觉数据库的状态将请求包放入命令队列中,以前是全部放进去,这样让其他的数据库操作不会随着数据库定时器而变慢,GS线程去驱动,一分钟不太可能还存不完 2.差异更新,GS只获取更改了的记录,这样不用每次都把全部的记录都放进去,这个过程是数据库改观不少 外面搞个mgr,内部使用可以重用的模板类,感觉是比之前封装的要好,但复杂度也上去了 m_spSaveOptMgr.reset(new SaveOptMgr(m_spAsyn

mysql定时数据备份工具(c#)

此博文的出处 为 http://blog.csdn.net/zhujunxxxxx/article/details/40124773如果进行转载请注明出处.本文作者原创,邮箱[email protected],如有问题请联系作者 为了确保数据的安全,我们往往要对数据进行备份.但是为了减少我们的工作量,我写了一个简单的数据备份工具,实现定时备份数据库. 其实程序很简单,数据备份的工作就是几个mysql的命令而已. 先看看程序的运行界面 可以看到界面是十分的简单的 我们使用的是命令行来进行数据备份,