定时任务实现Timer, TimeTask, ScheduledExecutorService及Spring定时器

原创 2015年11月21日 10:26:17

在工作中,我们往往会遇到这样的需求,那就是我们可能需要定期的去处理一个任务,如定期的清理一些文件、定期离线爬取一些数据等,更多的时候是在凌晨别人都进入梦乡的时候偷偷的完成某些任务,这时候我们就需要知道如何去实现一个定时任务了。


定时任务,简单的来说就是我们可以控制一个任务单元的执行时间和执行频率。那么有什么方法可以实现呢?下面我们从简单的开始说起。


(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 * * ?

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Java 并发专题 : Timer的缺陷 用ScheduledExecutorService替代

继续并发,上篇博客对于ScheduledThreadPoolExecutor没有进行介绍,说过会和Timer一直单独写一篇Blog. 1、Timer管理延时任务的缺陷 a、以前在项目中也经常使用定时器...

线程池中ScheduledExecutorService定时器功能(二)

java中的定时器功能   在jdk1.5之前,大家都用传统的定时器Timer来实现该功能 如,我们需要定制一个特殊方法,在程序首次载入时就执行,以后每隔一定的时间去执行那个方法 传统的做法如...

Java 定时任务 ScheduledExecutorService 的使用

/** * 定时执行任务 * @param time 格式 12:00:00 */ public static void executor(String time) { Sched...

Spring定时任务的实现方式--ScheduledExecutorService

关于为什么要使用java.util.concurrent.ScheduledExecutorService,请参考:http://blog.csdn.net/qq_32786873/article/d...

ScheduledExecutorService执行定时任务

ScheduledExecutorService   作者:赵磊 博客:http://elf8848.iteye.com   java.util.concurrent.Ex...
  • elf8848
  • elf8848
  • 2014年10月09日 10:45
  • 959

ScheduledExecutorService定时周期执行指定的任务

一:简单说明 ScheduleExecutorService接口中有四个重要的方法,其中scheduleAtFixedRate和scheduleWithFixedDelay在实现定时程序时比较方便。...

Java实现定时调度的三种方法

1、Timer Timer myTimer = new Timer();           myTimer.schedule(new Worker(), 1000);//1秒后执行   // ...

任务调度(三)——Timer的替代品ScheduledExecutorService简介

先前的两篇博文《任务调度(一)——jdk自带的Timer》和《任务调度(二)——jdk自带的Timer 动态修改任务执行计划》中,简单介绍了一下Timer,可以实现几本的功能,但是在多线程方面却略显...

ScheduledExecutorService定时周期执行指定的任务

一:简单说明 ScheduleExecutorService接口中有四个重要的方法,其中scheduleAtFixedRate和scheduleWithFixedDelay在实现定时程序时比较方...

Quartz和Spring Task定时任务的简单应用和比较

看了两个项目,一个用的是Quartz写的定时器,一个是使用spring的task写的,网上看了2篇文章,写的比较清楚,这里做一下留存 链接一、菠萝大象:http://www.blogjava.net...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:定时任务实现Timer, TimeTask, ScheduledExecutorService及Spring定时器
举报原因:
原因补充:

(最多只允许输入30个字)