在工作中,我们往往会遇到这样的需求,那就是我们可能需要定期的去处理一个任务,如定期的清理一些文件、定期离线爬取一些数据等,更多的时候是在凌晨别人都进入梦乡的时候偷偷的完成某些任务,这时候我们就需要知道如何去实现一个定时任务了。
定时任务,简单的来说就是我们可以控制一个任务单元的执行时间和执行频率。那么有什么方法可以实现呢?下面我们从简单的开始说起。
(1)Timer
JDK中自带的Timer和TimeTask,分别可以帮助我们实现一个定时器和一个定时任务。下面是简单的一个实现。
<span style="font-size:18px;">import java.util.Timer;
import java.util.TimerTask;
public class TimerTest
{
private static long start;
public static void main(String[] args) throws Exception
{
TimerTask task1 = new TimerTask()
{
@Override
public void run()
{
System.out.println("task1 invoked ! "
+ (System.currentTimeMillis() - start));
}
};
Timer timer = new Timer();
timer.schedule(task1, 1000);
}
} </span>
在上面这个例子中,启动一个定时任务延时1s后执行定时任务打印“task1 invoked ! ".
如果我们想周期执行这个任务,可以使用timer.scheduleAtFixedRate(task1,0, 1000),将每隔1s执行一次task1。打印结果如下所示:
Timer提供了6个相关的方法,具体如下所示:
使用Timer不好的地方在于其内部实现使用的是一个单线程在运行,如果我们给一个定时器赋予多个定时任务,那么就有可能出现问题,具体可以参看这篇文章http://blog.csdn.net/lmj623565791/article/details/27109467。
(2)ScheduledExecutorService和Executors
使用Timer跑多个定时任务时有时并不能满足相应的需求,这根Timer内部单线程设计有关,所以如果想跑多个定时任务,建议使用这里的ScheduledExecutorService,首先Executors创建一个线程池服务对象,然后将所有定时任务交给这个线程池去执行,这样就可以避免Timer的问题了。
ScheduleExecutorService接口中有四个重要的方法,其中scheduleAtFixedRate和scheduleWithFixedDelay在实现定时程序时比较方便。
下面是该接口的原型定义
java.util.concurrent.ScheduleExecutorService extends ExecutorService extends Executor
<span style="font-size:18px;">public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit); </span>
command:执行线程initialDelay:初始化延时
period:两次开始执行最小间隔时间
unit:计时单位
接口scheduleWithFixedDelay原型定义及参数说明
<span style="font-size:18px;">public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit); </span>
command:执行线程initialDelay:初始化延时
period:前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)
unit:计时单位
下面给出一些常用的示例:
二:功能示例
1.按指定频率周期执行某个任务。
初始化延迟0ms开始执行,每隔100ms重新执行一次任务。
/**
* 以固定周期频率执行任务
*/
public static void executeFixedRate() {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(
new EchoServer(),
0,
100,
TimeUnit.MILLISECONDS);
}
2.按指定频率间隔执行某个任务。
初始化时延时0ms开始执行,本次执行结束后延迟100ms开始下次执行。
/**
* 以固定延迟时间进行执行
* 本次任务执行完成后,需要延迟设定的延迟时间,才会执行新的任务
*/
public static void executeFixedDelay() {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleWithFixedDelay(
new EchoServer(),
0,
100,
TimeUnit.MILLISECONDS);
}
3.周期定时执行某个任务。
有时候我们希望一个任务被安排在凌晨2点(访问较少时)周期性的执行一个比较耗费资源的任务,可以使用下面方法设定每天在固定时间执行一次任务。
/**
* 每天晚上8点执行一次
* 每天定时安排任务进行执行
*/
public static void executeEightAtNightPerDay() {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
long oneDay = 24 * 60 * 60 * 1000;
long initDelay = getTimeMillis("02:00:00") - System.currentTimeMillis();
initDelay = initDelay > 0 ? initDelay : oneDay + initDelay;
executor.scheduleAtFixedRate(
new EchoServer(),
initDelay,
oneDay,
TimeUnit.MILLISECONDS);
}
/**
* 获取指定时间对应的毫秒数
* @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;
}
return 0;
}
这里,我们在转换日期成毫秒数的时候如果出现了异常会直接返回0,但是正常情况下我们需要对这种情况做一些特殊处理,如给个默认值或者在代码启动时给出预警,这样看具体情况具体处理了,我在工作中就是遇到配置的时间格式不正确的话一方面在启动日志中打印出相关警告信息,另一方面按照默认值“02:00:00”(因为我希望定时任务在凌晨执行)。
(3)Spring定时器功能
具体配置可以如下所示配置:
<bean id="myTimedTask" class="com.study.MyTimedTask"/>
<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>
<bean id="myTimedTaskTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="doMyTimedTask"/>
<property name="cronExpression" value="0 0 2 * * ?"/>
</bean>
<bean id="doScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref local="myTimedTaskTrigger"/>
</list>
</property>
</bean>
上述配置表示每天02:00:00出发做doMyTimedTask的execute()方法,配置也可以写成下面的这种方式:
<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>
下面收集了一些Spring定时器常用的时间设置方式,有需要的可以看下:
"0/10 * * * * ?" 每10秒触发
"0 0 12 * * ?" 每天中午12点触发
"0 15 10 ? * *" 每天上午10:15触发
"0 15 10 * * ?" 每天上午10:15触发
"0 15 10 * * ? *" 每天上午10:15触发
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
"0 15 10 15 * ?" 每月15日上午10:15触发
"0 15 10 L * ?" 每月最后一日的上午10:15触发
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?