Java定时任务 JDK 自带的定时器实现 Quartz 任务调度实现 Spring 相关的任务调度
-
JDK自带:JDK 自带的 Timer 以及 JDK1.5+ 新增的 ScheduledExecutorService;
-
Quartz:简单却强大的 JAVA 作业调度框架;
-
Spring3.0以后自带的task :可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多;
Timer
这个类允许你调度一个java.util.TimerTask任务。主要有以下几个方法:
-
-
schedule(TimerTask task, Date time) 特定时间执行
-
schedule(TimerTask task, long delay, long period) 延迟 delay 执行并每隔 period 执行一次。用固定延迟调度。使用本方法时,在任务执行中的每一个延迟会传播到后续的任务的执行。
-
scheduleAtFixedRate(TimerTask task, long delay, long period) 延迟 delay 执行并固定速率 period 执行一次。用固定比率调度。使用本方法时,所有后续执行根据初始执行的时间进行调度,从而希望减小延迟。
ScheduledExecutorService
该定时任务接口,主要有以下几个方法
-
-
<V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);
-
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);
-
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);
该接口的默认实现为 ScheduledThreadPoolExecutor 类,这个类继承了 ThreadPoolExecutor 类。线程池的使用使其比Timer更稳定。spring Task内部也是依靠它实现的。
Timer的缺陷
1、Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,后面的任务执行时间就被推迟。
package cn.boomoom.service; import java.util.Timer; import java.util.TimerTask; public class TimerTest { private static long start; public static void main(String[] args) { TimerTask task1 = new TimerTask() { @Override public void run() { System.out.println("task1 invoked ! " + (System.currentTimeMillis() - start)); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }; TimerTask task2 = new TimerTask() { @Override public void run() { System.out.println("task2 invoked ! " + (System.currentTimeMillis() - start)); } }; Timer timer = new Timer(); start = System.currentTimeMillis(); timer.schedule(task1, 1000); timer.schedule(task2, 3000); } }
定义了两个任务,预计是第一个任务1s后执行,第二个任务3s后执行,但是看运行结果:
task1 invoked ! 1000
task2 invoked ! 4001
task2实际上是4后才执行,正因为Timer内部是一个线程,而任务1所需的时间超过了两个任务间的间隔导致。
package cn.boomoom.service; import java.util.TimerTask; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ScheduledExecutorServiceTest { private static long start; public static void main(String[] args) { // 使用工厂方法初始化一个ScheduledThreadPool ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(2); TimerTask task1 = new TimerTask() { @Override public void run() { try { System.out.println("task1 invoked ! " + (System.currentTimeMillis() - start)); Thread.sleep(3000); } catch (Exception e) { e.printStackTrace(); } } }; TimerTask task2 = new TimerTask() { @Override public void run() { System.out.println("task2 invoked ! " + (System.currentTimeMillis() - start)); } }; start = System.currentTimeMillis(); newScheduledThreadPool.schedule(task1, 1000, TimeUnit.MILLISECONDS); newScheduledThreadPool.schedule(task2, 3000, TimeUnit.MILLISECONDS); } }
task1 invoked ! 1002
task2 invoked ! 3004
符合我们的预期结果。因为ScheduledThreadPool内部是个线程池,所以可以支持多个任务并发执行。
2、Timer当任务抛出异常时的缺陷。如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行。
package cn.boomoom.service; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimerTest01 { public static void main(String[] args) { final TimerTask task1 = new TimerTask() { @Override public void run() { throw new RuntimeException(); } }; final TimerTask task2 = new TimerTask() { @Override public void run() { System.out.println("task2 invoked!"); } }; Timer timer = new Timer(); timer.schedule(task1, 100); timer.scheduleAtFixedRate(task2, new Date(), 1000); } }
task2 invoked! Exception in thread "Timer-0" java.lang.RuntimeException at cn.itcast.bos.service.TimerTest01$1.run(TimerTest01.java:14) at java.util.TimerThread.mainLoop(Timer.java:555) at java.util.TimerThread.run(Timer.java:505)
由于任务1的一次,任务2也停止运行了。
package cn.boomoom.service; import java.util.TimerTask; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ScheduledExecutorServiceTest01 { public static void main(String[] args) { final TimerTask task1 = new TimerTask() { @Override public void run() { throw new RuntimeException();} }; final TimerTask task2 = new TimerTask() { @Override public void run() { System.out.println("task2 invoked!"); } }; ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); pool.schedule(task1, 100, TimeUnit.MILLISECONDS); pool.scheduleAtFixedRate(task2, 0, 1000, TimeUnit.MILLISECONDS); } }
task2 持续输出。ScheduledExecutorService 保证了,task1出现异常时,不影响task2的运行。
3、Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化会出现一些执行上的变化,ScheduledExecutorService 基于时间的延迟,不会由于系统时间的改变发生执行变化。
Quartz 任务调度实现
quartz 的 java demo
http://www.quartz-scheduler.org/ 入门案例: http://www.quartz-scheduler.org/documentation/quartz-2.1.x/quick-start.html
1、导入maven坐标
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.boomoom.maven</groupId> <artifactId>quartz_helloworld</artifactId> <version>0.0.1-SNAPSHOT</version> <name>quartz_helloworld</name> <dependencies> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency> </dependencies> </project>
2、quartz demo
package cn.boomoom.service; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import java.util.HashMap; import java.util.Map; public class QuartzTest { public static void main(String[] args) throws SchedulerException { SchedulerFactory sf = new StdSchedulerFactory(); Scheduler scheduler = sf.getScheduler(); Map<JobDetail, Trigger> jobTriggerMap = jobAndTrigger(); for (Map.Entry<JobDetail,Trigger> entry : jobTriggerMap.entrySet()) { scheduler.scheduleJob(entry.getKey(), entry.getValue()); } scheduler.start(); } private static Map<JobDetail, Trigger> jobAndTrigger() { HashMap<JobDetail, Trigger> hashMap = new HashMap<>(); JobDetail job1 = JobBuilder.newJob(HelloJob1.class).withIdentity("job1", "group1").build(); Trigger trigger1 = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow() .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build(); hashMap.put(job1, trigger1); JobDetail job2 = JobBuilder.newJob(HelloJob2.class).withIdentity("job2", "group1").build(); Trigger trigger2 = TriggerBuilder.newTrigger().forJob("job2", "group1").startNow() .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).build(); hashMap.put(job2, trigger2); JobDetail job3 = JobBuilder.newJob(HelloJob3.class).withIdentity("job3", "group1").build(); Trigger trigger3 = TriggerBuilder.newTrigger().forJob("job3", "group1").startNow() .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")).build(); hashMap.put(job3,trigger3); return hashMap; } }
JobDetail、Trigger、Scheduler三个对象为Quartz主要对象。启动Trigger的定时方式由不同的ScheduleBuilder子类提供,如:SimpleScheduleBuilder、CronScheduleBuilder、DailyTimeIntervalScheduleBuilder、CalendarIntervalScheduleBuilder。
spring 集成 quartz
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.boomoom.maven</groupId> <artifactId>quartz_spring</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>quartz_spring</name> <dependencies> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.1.7.RELEASE</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>tomcat-maven-plugin</artifactId> <version>1.1</version> <configuration> <port>9888</port> </configuration> </plugin> </plugins> </build> </project>
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <!-- spring配置文件位置 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- spring核心监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
package cn.boomoom.job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.quartz.QuartzJobBean; import cn.boomoom.service.HelloService; public class HelloJob extends QuartzJobBean { @Autowired private HelloService helloService; @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { helloService.sayHello(); } }
package cn.boomoom.service; import org.springframework.stereotype.Service; @Service public class HelloService { public void sayHello() { System.out.println("hello,quartz service !"); } }
package cn.boomoom.job; import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.scheduling.quartz.AdaptableJobFactory; import org.springframework.stereotype.Service; @Service("jobFactory") public class JobFactory extends AdaptableJobFactory { @Autowired private AutowireCapableBeanFactory capableBeanFactory; @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { // 调用父类的方法 Object jobInstance = super.createJobInstance(bundle); // 进行注入 capableBeanFactory.autowireBean(jobInstance); return jobInstance; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <context:component-scan base-package="cn.boomoom" /> <bean > <!-- Job实现类的全路径 --> <property name="jobClass" value="cn.boomoom.job.HelloJob"/> <property name="jobDataAsMap"> <map> <entry key="timeout" value="5"/> </map> </property> </bean> <!-- ======================== 调度触发器 ======================== --> <bean > <property name="jobDetail" ref="helloJob"></property> <property name="cronExpression" value="0 54 * * * ?"></property> </bean> <!-- ======================== 调度工厂 ======================== --> <!-- Scheduler自动运行,不用定义id --> <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <!-- 在Job中spring管理的Bean无法注入,需要在Scheduler中自定义JobFactory --> <property name="jobFactory" ref="jobFactory" /> <property name="triggers"> <list> <ref bean="cronTriggerBean" /> </list> </property> </bean> </beans>
spring提供了JobDetail、Trigger、Scheduler三个的factoryBean。
在 Job 中 spring 管理的 Bean 无法注入解决,需要在 Scheduler 中自定义 JobFactory。jobFactory 在创建job对象的时候,为其指定了 spring自动注入的属性,(类中的@Autowired)。
JobFactory 指定定义的时候,上面的方式有些时候不行,可以用下面的方式
import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.scheduling.quartz.SpringBeanJobFactory; @Service("jobFactory") public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { private transient AutowireCapableBeanFactory beanFactory; @Override public void setApplicationContext(final ApplicationContext context) { beanFactory = context.getAutowireCapableBeanFactory(); } @Override protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception { final Object job = super.createJobInstance(bundle); beanFactory.autowireBean(job); return job; } }
JobFactory 参考1,参考2. quartz定时任务实现只执行一次,SimpleTriggerFactoryBean
Spring 相关的任务调度
Spring 3.0+ 自带的任务调度实现,主要依靠TaskScheduler接口的几个实现类实现。
主要用法有以下三种:
配置文件实现
spring-schedule.xml
<task:scheduler id="myScheduler" pool-size="10" /> <task:scheduled-tasks scheduler="myScheduler"> <task:scheduled ref="job" method="test" cron="0 * * * * ?"/> </task:scheduled-tasks>
注解实现
spring-schedule.xml
<task:scheduler id="myScheduler" pool-size="10" /> // 启用注解 <task:annotation-driven scheduler="myScheduler"/>
@Component public class ScheduleTask { // 每隔5秒执行一次 @Scheduled(cron = "0/5 * * * * ?") public void printSay() { System.out.println("每隔5秒执行一次:" + new Date()); } }
代码动态添加
spring-schedule.xml
<bean id = "myScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler"> <property name="poolSize" value="10"/> <property name="threadGroupName" value="myScheduler" /> <property name="threadNamePrefix" value="-1" /> </bean> <task:annotation-driven scheduler="myScheduler"/>
@Component public class Test { @Autowired private ThreadPoolTaskScheduler myScheduler; public void addJob(){ myScheduler.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " run "); } } , new CronTrigger("0/5 * * * * ? ")); //每5秒执行一次 } }