欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring-JDK Timer 以及在Spring(4.0以下)中使用JDK Timer

程序员文章站 2022-06-09 15:22:53
...

概述

在Jdk1.3之后的版本中,通过java.util.Timer和java.util.TimerTask这两个类提供了简单的任务调度功能,称之为Java Timer.

JavaTimer允许按照固定的频率重复执行某项任务,比直接通过线程程序进行任务调度要轻松些,但是对于诸如“每周周一9:00执行”或者和日历相关的任务调度就无能为力了

此外,JDK Timer只适合对执行时间非常短的任务进行调度,因为在Timer中所有的TimerTask都在同一个背景线程中执行,长时间的任务会影响Timer的调度工作。

如果业务超过Java Timer的能力范围,建议使用Quartz框架。


Timer 和 TimerTask

  • TimerTask表示一个需要多次执行的任务,它实现了Runnable接口,可以在run方法中定义业务逻辑

  • Timer负责指定调度规则并调度TimerTask


抽象类TimerTask

Spring-JDK Timer 以及在Spring(4.0以下)中使用JDK Timer

TimerTask相当于Quartz中的Job, 代表一个被调度的任务。

二者的区别在于,每当执行任务时,Quartz都会创建一个JOb实例,而Jdk Timer则使用相同的TimerTask实例。 所以如果TimerTask类中拥有状态,那么这些状态对于后面的执行是可见的。 从这一点上来讲,TimerTask更像StatefulJob而非Job。

TimerTask抽象类中只有三个方法

  • public abstract void run() 子类覆盖这个方法并定义任务的执行逻辑,每次执行任务时,run方法都会被调用一次

  • public boolean cancel()取消任务,假设任务被安排执行N次,那么在调用该方法后,后续的执行安排将取消

  • public long scheduledExecutionTime()返回此任务的计划执行时间点。该方法一般在run方法内部调用,用户可以通过该方法判断本次执行的时间点是否过晚,并据此决定是否需要取消本次执行。该方法一般在固定执行频率执行时才有意义。


Timer

Timer只能以如下方式对任务进行调度:在延迟一段时间或者在指定时间点后运行一次任务或周期性的运行任务.

实际上,在Timer内部使用Object#wait(long time)进行任务的时间调度。这种机制不能保证任务的实时执行,只是一个粗略的近似值。

每个Timer对象那个都有一个对应的“背景线程”,它负责调度并执行Timer中所有的TimerTask。 由于所有的TimerTask都在这线程中执行,所以TimerTask的执行时间应该比较短。 如果一个TimerTask的执行占用了过多的时间,后面的任务就会受到影响。 由于后续任务在调度时间上受到了“挤压”,所以可能会造成扎堆执行的情况。

当Timer中所有的TimerTask已经执行完成并且Timer对象没有外部引用时,Timer的任务执行线程才回结束,但这可能需要很长的时间。 因此Timer在默认情况下适用daemon Thread,这样用户就可以在应用程序中通过Time#cancel方法手工结束Timer.

如果希望尽快结束Timer中的任务,则可以调用TimerTask#cancel方法手工达到目的。

Java有两种线程:用户线程(User Thread)和守护线程(Daemon Thread).

守护线程是在在程序后台运行,提供了一种通用服务线程,垃圾回收线程就是一种典型的守护线程。

守护线程是为主线程服务的,因此当所有的用户线程都结束时,守护线程会自动终止,因此,也可以将守护线程形象的称为奴仆线程,“主存我存,主亡我死”

将线程转换为守护线程可以通过Thread对象的setDaemon(true)方法来实现


Timer构造函数及方法

Timer的构造函数在创建Timer对象的同时将启动一个Timer背景线程。

Spring-JDK Timer 以及在Spring(4.0以下)中使用JDK Timer

我们先来看下Timer的构造函数

  • public Timer():创建一个Timer,背景线程是非守护线程

  • public Timer(String name):与Timer类似,只是通过name为关丽娜背景线程指定名称

  • public Timer(boolean isDaemon) :创建一个Timer,当isDaemon为true时,背景线程为守护线程,守护线程将在应用程序主线程停止后自动退出。该方法为Java5.0新增的。

  • public Timer(String name, boolean isDaemon):与Timer(boolean isDaemon) 相似并为关联背景线程指定名称,该方法是5.0新增的


通过以下方法执行一次任务

  • public void schedule(TimerTask task, Date time) 在特定时间点执行一次任务

  • public void schedule(TimerTask task, long delay) 延迟指定时间后执行一次任务,delay的单位为毫秒


通过如下方法按照固定间隔执行任务,间隔时长为上次任务执行完成的时间点到下次任务开始执行的时间点,任务的执行可能产生时间的漂移

  • public void schedule(TimerTask task, Date firstTime, long period)从指定时间点开始周期性的执行任务,period的单位为毫秒,后一次执行将在前一次执行完成后开始计时。 比如任务安排每2S执行一次,假设第一次任务在0秒时间点开始执行并花费了1.5S , 这第二次将在第3.5秒时执行

  • public void schedule(TimerTask task, long delay, long period) 在延迟执行的时间后,周期性的执行任务


通过以下方法按照固定的频率执行任务

  • public void scheduleAtFixedRate(TimerTask task, Date firstTime,
    long period) 在指定时间点后,以指定频率执行任务。 假设一个任务被安排每2S执行一次,如果第一次执行花费了1.5S , 则在0.5S后,第二次任务开始执行,以保证固定的执行频率

  • public void scheduleAtFixedRate(TimerTask task, long delay, long period) 在延迟一段时间后,以指定频率执行任务。


此外Timer还有几个控制方法

  • cancel :取消Timer的执行,并丢弃所有被调度的TimerTask不过正在执行的不受影响。 Timer被取消后,不能调度新的TimerTask

  • purge:将所有已经取消的TimerTask从Timer队列中清除。 如果TimerTask没有外部引用,那就可以被垃圾回收。 一般情况下无须调用该方法,只有在某些特定情况下,当一次性取消多个TimerTask后,调用该方法才有意义


示例

Job

package com.xgj.quartz.jdkTimer.jdkTimer;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

/**
 * 
 * 
 * @ClassName: MyTask
 * 
 * @Description: 任务执行10次后退出
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年11月17日 下午5:01:32
 */
public class MyTask extends TimerTask {

    int count = 0;

    @Override
    public void run() {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        System.out.println("Task begins to execute,execute times:" + count);

        Date date = new Date(scheduledExecutionTime());

        System.out.println("本次任务执行时间点为:" + sdf.format(date));

        // 执行10次后退出
        if (++count > 10) {
            cancel();
            System.out.println("Task exits");
        }


    }

}

调度类

package com.xgj.quartz.jdkTimer.jdkTimer;

import java.util.Timer;

public class TimerRunner {

    public static void main(String[] args) {
        Timer timer = new Timer();
        MyTask myTask = new MyTask();
        // 延迟1秒,每5S执行一次任务
        timer.schedule(myTask, 1000L, 5000L);

    }
}

运行结果

Task begins to execute,execute times:0
本次任务执行时间点为:2017-11-18 12:35:55
Task begins to execute,execute times:1
本次任务执行时间点为:2017-11-18 12:36:00
Task begins to execute,execute times:2
本次任务执行时间点为:2017-11-18 12:36:05
Task begins to execute,execute times:3
本次任务执行时间点为:2017-11-18 12:36:10
Task begins to execute,execute times:4
本次任务执行时间点为:2017-11-18 12:36:15
Task begins to execute,execute times:5
本次任务执行时间点为:2017-11-18 12:36:20
Task begins to execute,execute times:6
本次任务执行时间点为:2017-11-18 12:36:25
Task begins to execute,execute times:7
本次任务执行时间点为:2017-11-18 12:36:30
Task begins to execute,execute times:8
本次任务执行时间点为:2017-11-18 12:36:35
Task begins to execute,execute times:9
本次任务执行时间点为:2017-11-18 12:36:40
Task begins to execute,execute times:10
本次任务执行时间点为:2017-11-18 12:36:45
Task exits

Spring对Java Timer的支持 (Spring4.0+已经不支持了,推荐使用Quartz)

Spring在org.springframework.scheduling.timer中提供了几个JDK Timer的支持类,主要在以下3个方面:

  • ScheduledTimerTask对TimerTask提供封装并提供相关的配置

  • 通过MethodInvokingTimerTaskFactoryBean类可以将一个Bean的方法封装为TimerTask

  • 通过TimerFactoryBean可以更方便的配置Timer。此外,让Timer的生命周期和Spring容器的生命周期相关,在初始化TimerFactory后启动Timer,在Spring容器关闭前取消Timer

ScheduledTimerTask

JDKTimer标准的API要求在使用Timer方法进行任务调度时才指定调度规则,不符合Bean的配置,Spring为此提供了ScheduledTimerTask,通过属性指定任务和调度规则。

<bean id="scheduleTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
        <property name="timerTask" ref="timeTask" /><!--指定调度任务 -->
        <property name="delay" value="1000" /> <!--延迟时间,单位为毫秒 -->
        <property name="period" value="1000" /> <!--周期时间,单位为毫秒 -->
    </bean>

如果希望只运行一次,则将period设置为0或者是负值。

默认情况下,采用固定时间间隔的调度方式,可以通过fixRate属性设置以固定频率的方式执行任务。

SimpleTimerTask还可以将实现了Runnable接口的类封装成一个任务,用户可以通过runnable属性进行设置。


MethodInvokingTimerTaskFactoryBean

类似Quartz的MethodInvokingJobDetailFactoryBean,Spring也为 JDK Timer提供了一个方便类,用于将Bean封装成一个TimerTask.

使用Spring 时,我们并不一定要继承TimerTask 来定义一个任务,Spring 提供 org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean,如下所示

package com.xgj.quartz.jdkTimer.springJdkTimer2;

import java.text.SimpleDateFormat;
import java.util.Date;


public class MyJobService {

    public void doJob() {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        System.out.println("本次任务执行时间点为:" + sdf.format(new Date()));

    }

}

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myJobService" class="com.xgj.quartz.jdkTimer.springJdkTimer2.MyJobService" />

    <!-- Spring4.0以上版本已经不支持ScheduledTimerTask,而是推荐使用Quartz -->

    <bean id="scheduleTask" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
        <property name="targetObject" ref="myJobService" />
        <property name="targetMethod" value="doJob" /> 
    </bean>


     <bean id="scheduledTimerTask"    
          class="org.springframework.scheduling.timer.ScheduledTimerTask">    
        <property name="timerTask" ref="scheduleTask"/>    
        <property name="period" value="5000"/>    
         <property name="delay" value="1000"/>        
    </bean>    


    <bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
        <property name="scheduledTimerTasks">
            <list>
                <ref bean="scheduledTimerTask" />
            </list>
        </property>
    </bean>


</beans>

TimerFactoryBean

类似于Quartz的SchedulerFactoryBean,Spring为Timer提供了TimerFactoryBean类。 用户可以将多个ScheduledTimerTask注册到TimerFactoryBean中,TimerFactoryBean将返回一个Timer实例。 在初始化TimerFactory后启动Timer,在Spring容器关闭前取消Timer

<bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
        <property name="scheduledTimerTasks">
            <list>
                <ref bean="scheduleTask" />
            </list>
        </property>
    </bean>

scheduledTimerTasks的属性类型为 ScheduledTimerTask[]可以注入多个ScheduledTimerTask .

此外TimerFactoryBean还有个daemon属性,指定生成的Timer的背景线程是否是守护线程。


示例

为了演示该功能,我们需要先引入4.0以下的Spring依赖,这里我们使用

修改pom.xml中的如下信息

<spring.version>3.2.18.RELEASE</spring.version>

官方说明:
https://docs.spring.io/spring/docs/3.0.x/javadoc-api/org/springframework/scheduling/timer/ScheduledTimerTask.html

Spring-JDK Timer 以及在Spring(4.0以下)中使用JDK Timer

Job

package com.xgj.quartz.jdkTimer.springJdkTimer;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

/**
 * 
 * 
 * @ClassName: MyTask
 * 
 * @Description: 任务执行10次后退出
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年11月17日 下午5:01:32
 */
public class MyTask extends TimerTask {

    int count = 0;

    @Override
    public void run() {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        System.out.println("Task begins to execute,execute times:" + count);

        Date date = new Date(scheduledExecutionTime());

        System.out.println("本次任务执行时间点为:" + sdf.format(date));

        // 执行10次后退出
        if (++count > 10) {
            cancel();
            System.out.println("Task exits");
        }


    }

}

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="timeTask" class="com.xgj.quartz.jdkTimer.springJdkTimer.MyTask" />

    <!-- Spring4.0以上版本已经不支持ScheduledTimerTask,而是推荐使用Quartz -->

    <bean id="scheduleTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
        <property name="timerTask" ref="timeTask" />
        <property name="delay" value="1000" /> <!--延迟1s -->
        <property name="period" value="1000" /> <!--1s一次 -->
        <property name="fixedRate" value="true" />
    </bean>

    <bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
        <property name="scheduledTimerTasks">
            <list>
                <ref bean="scheduleTask" />
            </list>
        </property>
    </bean>


</beans>

测试类(这种定时任务,写单元测试只会执行一次,这里我们写个简单的main方法)

package com.xgj.quartz.jdkTimer.springJdkTimer;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringJdkTimerTest {

    public static void main(String[] args) {

        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                "classpath:com/xgj/quartz/jdkTimer/springJdkTimer/spring-jdkTimer.xml");
        System.out.println("initContext successfully");

        MyTask task = ctx.getBean("timeTask", MyTask.class);
        task.run();

    }

}

运行结果

2017-11-18 12:25:20,564  INFO [main] (AbstractApplicationContext.java:518) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@88b6cca: startup date [Sat Nov 18 12:25:20 BOT 2017]; root of context hierarchy
2017-11-18 12:25:20,658  INFO [main] (XmlBeanDefinitionReader.java:316) - Loading XML bean definitions from class path resource [com/xgj/quartz/jdkTimer/springJdkTimer/spring-jdkTimer.xml]
2017-11-18 12:25:21,084  INFO [main] (DefaultListableBeanFactory.java:605) - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@381702ff: defining beans [timeTask,scheduleTask,timerFactory]; root of factory hierarchy
2017-11-18 12:25:21,230  INFO [main] (TimerFactoryBean.java:97) - Initializing Timer
initContext successfully
Task begins to execute,execute times:0
本次任务执行时间点为:2017-11-18 12:25:21
Task begins to execute,execute times:1
本次任务执行时间点为:2017-11-18 12:25:22
Task begins to execute,execute times:2
本次任务执行时间点为:2017-11-18 12:25:23
Task begins to execute,execute times:3
本次任务执行时间点为:2017-11-18 12:25:24
Task begins to execute,execute times:4
本次任务执行时间点为:2017-11-18 12:25:25
Task begins to execute,execute times:5
本次任务执行时间点为:2017-11-18 12:25:26
Task begins to execute,execute times:6
本次任务执行时间点为:2017-11-18 12:25:27
Task begins to execute,execute times:7
本次任务执行时间点为:2017-11-18 12:25:28
Task begins to execute,execute times:8
本次任务执行时间点为:2017-11-18 12:25:29
Task begins to execute,execute times:9
本次任务执行时间点为:2017-11-18 12:25:30
Task begins to execute,execute times:10
本次任务执行时间点为:2017-11-18 12:25:31
Task exits

示例源码

代码已托管到Github—> https://github.com/yangshangwei/SpringMaster