任务调度:
Spring Task:
Spring Task 是Spring 3.0提供的定时任务解决方案,不仅提供单线程和多线程两种执行任务的方式,而且还提供了线程池的方式合理利用资源。SpringTask提供注解和xml配置的方式进行和SpringBoot无缝连接。
Spring Boot 默认在无任何第三方依赖的情况下使用 spring-context
模块下提供的定时任务工具 Spring Task。我们只需要使用 @EnableScheduling
注解就可以开启相关的定时任务功能。如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling @SpringBootApplication @MapperScan("com.yeb.mapper") public class YebServerApplication { public static void main(String[] args) { SpringApplication.run(YebServerApplication.class, args); }
}
|
只要在方法上加上@Scheduled 注解就可以定义任务;在类上加上@Component注解,Spring 容器就可以自动执行任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;
@Component public class TestTask {
@Scheduled(cron = "*/2 * * * * ?") public void task1() throws InterruptedException { System.out.println("Task1开始,线程 id=>" + Thread.currentThread().getId()); Thread.sleep(10000); System.out.println("Task任务结束,线程 id=》"+ Thread.currentThread().getId()); }
@Scheduled(cron = "*/4 * * * * ?") public void task2() throws InterruptedException { System.out.println("Task2开始,线程 id=>" + Thread.currentThread().getId()); Thread.sleep(2000); System.out.println("Task2结束,线程 id =>" + Thread.currentThread().getId()); } }
|
运行结果:
通过结果可以看出,可以发现两个任务都是由同一个线程执行的。这就会引发一个问题,Task1需要10执行完,而Task2两秒就能够执行完。当执行Task1时,本应该执行5次Task2,但是却因为Task1任务的线程占用,少执行4次,因为Task1的10秒执行的时间超出了同期望执行的时间的2秒,所以已没有办法进行2秒执行一次。这就是单线程执行多任务时的弊端。
将同步任务修改为异步任务,只需要在原代码的基础上加上两个注解。在启动类上加@EnableAsync注解
1 2 3 4 5 6 7 8 9 10 11 12 13
| @EnableScheduling @EnableAsync @SpringBootApplication @MapperScan("com.yeb.mapper") public class YebServerApplication {
public static void main(String[] args) { SpringApplication.run(YebServerApplication.class, args); }
}
|
在任务类上加入@Async注解
1 2 3 4 5 6 7 8 9 10 11 12
|
@Async @Scheduled(cron = "*/2 * * * * ?") public void task1() throws InterruptedException { System.out.println("Task1开始,线程 id=>" + Thread.currentThread().getId()); Thread.sleep(10000); System.out.println("Task任务结束,线程 id=》"+ Thread.currentThread().getId()); }
|

观察 Task1和Task2任务,会发现每一个新的任务都会一个新的线程来进行执行
利用 线程池可以更好的利用线程资源。
在config类中创建 TaskConfig,并在其中配置具有5个线程的线程池来执行任务调度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor; import java.util.concurrent.Executors;
@Configuration public class TaskConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskExecutor()); }
@Bean public Executor taskExecutor() { return Executors.newScheduledThreadPool(10); } }
|

可以发现任务的调度不在随意创建新的线程,而是会利用线程池的线程资源,提高效率
SpringTask 简单易用,在Springboot中,不需要对application.xml做任何的配置,也不要导入额外的依赖。
Quartz:
Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现。无论是单任务调度还是成千上万的任务调度,甚至是分布式集群任务调度,都可以游刃有余。
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
|
特点:
- 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
- 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
- 分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。

- Scheduler:任务调度器,是实际执行任务调度的控制器
- Trigger:触发器,用于定义任务调度的时间规则
- Job:任务,即被调度的任务
Quartz调度的核心元素:
Job接口:只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。实现Job接口的任务,默认是无状态的,若要将Job设置成有状态的,在quartz中是给实现的Job添加@DisallowConcurrentExecution注解(以前是实现StatefulJob接口,现在已被Deprecated),在与spring结合中可以在spring配置文件的job detail中配置concurrent参数。
JobDetail: 用来描述Job实现类以及其他的相关静态操作,如Job名字、关联监听器等信息。在spring中有JobDetailFactoryBean和 MethodInvokingJobDetailFactoryBean两种实现,如果任务调度只需要执行某个类的某个方法,就可以通过MethodInvokingJobDetailFactoryBean来调用。一个JobDetail有多个Trigger,但是一个Trigger 只能对应一个JobDetail.
Trigger:触发器,用于定义任务调度的时间规则,有SimpleTrigger,CronTrigger,DateIntervalTrigger和NthIncludedDayTrigger,当仅当需要触发一次时,或者以固定的时间间隔周期执行时,SimpleTriggers是最合适的选择,其中CronTrigger用的比较多,CronTrigger在spring中封装在CronTriggerFactoryBean中。
Scheduler:任务调度器:是实际执行任务调度的控制器。在spring中通过SchedulerFactoryBean封装起来。Trigger和JobDetail可以被注册到Scheduler中,Scheduler可以将Trigger绑定到某一个JobDetail中,Scheduler可以将Trigger绑定到一个JobDetail中,这样当Trigger被触发时,对应的Job就会被执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean;
public class TestJob1 extends QuartzJobBean {
@Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { System.out.println("Job1开始,线程 id=>" + Thread.currentThread().getId()); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Job1结束,线程 id=>" + Thread.currentThread().getId()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean;
public class TestJob2 extends QuartzJobBean {
@Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { System.out.println("Job2开始,线程 id=>" + Thread.currentThread().getId()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Job2结束,线程 id=>" + Thread.currentThread().getId()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package com.yeb.config;
import com.yeb.utils.TestJob1; import com.yeb.utils.TestJob2; import org.quartz.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class QuartzConfig { public class Job1 { @Bean public JobDetail createJobDetail1() { return JobBuilder.newJob(TestJob1.class).withIdentity("TestJob1").storeDurably().build(); }
@Bean public Trigger createTrigger1() { SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(2) .repeatForever(); SimpleTrigger trigger = TriggerBuilder.newTrigger().forJob(createJobDetail1()) .withIdentity("TestJob1Trigger") .withSchedule(scheduleBuilder) .build(); return trigger; }
public class Job2 { @Bean public JobDetail testJob2() { return JobBuilder.newJob(TestJob2.class).withIdentity("TestJob2").storeDurably().build(); }
@Bean public Trigger testTrigger2() { SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(4) .withRepeatCount(3); SimpleTrigger trigger= TriggerBuilder.newTrigger().forJob(testJob2()) .withIdentity("TestJob2Trigger") .withSchedule(scheduleBuilder) .build(); return trigger;
} } } }
|
两次任务调度中使用了SimpleTrigger ,其中将Job1设置成了2秒执行一次,执行无限次;将Job2设置为每2秒执行一次,执行无限次数;将Job设置为每4秒执行一次,共执行3次。
Cron介绍
在线Cron表达式生成器 (qqe2.com)
cron是linux系统中用来定期执行命令或指定程序任务的一种服务或软件。可以满足周期性执行任务的需求
corn表达式格式为七个域,从左到右进行记数,使用空格间隔
1
| corn表达式格式为七个域,如: 秒 分 时 日 月 周 年
|

“/”:表示从几开始,每隔固定时间执行一次。比如说:
- 在分钟字段下的值是
0/25
,指从第 0 分钟开始,每 25 分钟执行一次
- 在分钟字段下的值是
3/4
,指从第 3 分钟开始,每 4 分钟执行一次
这里有一个点要注意:cron 表达式不会记录上次执行的时间,因此上面的 0/25 的执行时间为 0:0, 0:25, 0:50, 1:0, 1:25, 1:50
,而不是从当前时间向后推 25 分钟执行。这可能和你印象中的执行时间不大一样。
“?”:表示每月的某一天,或第几周的某一天。表示不确定的值
“L”:用于每月,或每周,表示为每月的最后一天,或每个月的最后星期几如“6L”表示“每月的最后一个星期五”
“W”:表示为最近工作日(周一到周五,国外没有调休),如“15W”放在每月(day-of-month)字段上表示为“到本月15日最近的工作日”
“#”:是用来指定该月第几个周 X。比如说:
- 在每周字段下的值是
6#3
, 则表示“每月第三个星期五”
“,”字符:指定数个值,每个值之间用逗号分隔。
“-”字符:指定一个值的范围,比如说:
执行时间为:0, 1, 2, 3, 4, 5
“L”字符:用在日表示一个月中的最后一天,用在周表示该月最后一个星期 X
