首页 > 应用开发 > Java > Java培训-实现定时任务的几种方式
以下文章来源于架构师必备
一、ScheduledThreadPool
ScheduledThreadPool是JDK自带的类,可以用来替代 Timer类实现定时任务。一个 Timer只能执行一个任务,而一个 ScheduledThreadPool却可以同时执行多个定时任务。用法很简单,直接看例子:
public class ScheduledThreadPoolService {
private Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
// 参数代表可以同时执行的定时任务个数
private ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
/**
* schedule:延时2秒执行一次任务
*/
public void task0() {
service.schedule(() -> {
logger.info("task0-start");
sleep(2);
logger.info("task0-end");
}, 2, TimeUnit.SECONDS);
}
/**
* scheduleAtFixedRate:2秒后,每间隔4秒执行一次任务
* 注意,如果任务的执行时间(例如6秒)大于间隔时间,则会等待任务执行结束后直接开始下次任务
*/
public void task1() {
service.scheduleAtFixedRate(() -> {
logger.info("task1-start");
sleep(2);
logger.info("task1-end");
}, 2, 4, TimeUnit.SECONDS);
}
/**
* scheduleWithFixedDelay:2秒后,每次延时4秒执行一次任务
* 注意,这里是等待上次任务执行结束后,再延时固定时间后开始下次任务
*/
public void task2() {
service.scheduleWithFixedDelay(() -> {
logger.info("task2-start");
sleep(2);
logger.info("task2-end");
}, 2, 4, TimeUnit.SECONDS);
}
private void sleep(long time) {
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
和线程池的使用类似,也是通过 Executors类来创建 ScheduledThreadPool,常用的方法例子中都有说明,简单的定时任务使用 ScheduledThreadPool实现就可以了,也不用引入其它依赖,还是挺方便的_ 。
二、 @Scheduled
@Scheduled是Spring框架的定时任务实现, 【关注尚硅谷,轻松学IT】相比JDK的 ScheduledThreadPool功能更加强大。可以先创建一个Spring Boot项目,在启动类上添加 @EnableScheduling注解,使 @Scheduled生效,开启定时任务:
@SpringBootApplication
@EnableScheduling
public class LearnSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(LearnSpringbootApplication.class, args);
}
}
然后就是定义任务类,用 @Scheduled配置具体的定时规则:
@Service
public class ScheduleService {
private Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
/**
* fixedRate:每间隔2秒执行一次任务
* 注意,默认情况下定时任务是在同一线程同步执行的,如果任务的执行时间(例如6秒)大于间隔时间,则会等待任务执行结束后直接开始下次任务
*/
@Scheduled(fixedRate = 2000)
public void task0() {
logger.info("task0-start");
sleep(6);
logger.info("task0-end");
}
/**
* fixedDelay:每次延时2秒执行一次任务
* 注意,这里是等待上次任务执行结束后,再延时固定时间后开始下次任务
*/
@Scheduled(fixedDelay = 2000)
public void task1() {
logger.info("task1-start");
sleep(6);
logger.info("task1-end");
}
/**
* initialDelay:首次任务启动的延时时间
*/
@Scheduled(initialDelay = 2000, fixedDelay = 3000)
public void task2() {
logger.info("task2-start");
sleep(6);
logger.info("task2-end");
}
private void sleep(long time) {
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
fixedRate、 fixedDelay、 initialDelay属性的时间单位是 毫秒,用法也比较简单,可参考代码的注释。
这里我们重点看使用 @Scheduled的 cron属性定义定时任务的时间表达式,它的功能更加丰富。先看一个简单的例子:
@Service
public class ScheduleService {
...................
/**
* 每天晚上23:59执行一次
*/
@Scheduled(cron = "0 59 23 * * ?")
public void task3() {
logger.info("task3-start");
sleep(6);
logger.info("task3-end");
}
...................
}
接下来具体看cron属性对应时间表达式的定义规则:
1、按顺序依次是:秒、分、时、日、月、周,中间用空格间隔
2、月、周可以用数字或英文单词的前三个字母表示
3、日和周可能会冲突,因此两个可以有一个配置为`?`
4、常用通配符的含义:
5、几个表达式的例子:
6、注意,不同版本的Spring对cron属性的支持有所差异,例如较新的版本中cron属性只支持6个字段,不包含年份;同时#、L、W通配符也不支持。
7、注意,@Scheduled默认会使用同一线程同步的去执行所有定时任务,要打破这一点,可以在Spring Boot启动类上配置@EnableAsync注解来开启异步线程可用,然后在定时任务方法上配置@Async注解,则会使用异步线程去执行定时任务。
三、Quartz
除了上边的方式之外,我们还有更好的选择,那就是Quartz,在Spring Boot中集成Quartz需要先添加如下Maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
和@Scheduled一样,也需要在启动类添加@EnableScheduling注解来开启定时任务:
@SpringBootApplication
@EnableScheduling
public class LearnSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(LearnSpringbootApplication.class, args);
}
}
这样准备工作就完成了。接下来先了解几个重要的概念:
接下来通过Quartz实现定时任务,可以按照上边几个重要概念的顺序来完成。
1、定义Job
定义Job有两种方式,
第一种是直接定义任务类,并注册到Spring IoC容器中:
@Service
public class QuartzJob {
private Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
public void hello(){
logger.info("job0-start");
Utils.sleep(TimeUnit.SECONDS, 4);
logger.info("job0-end");
}
}
第二种是继承QuartzJobBean,重写executeInternal方法,这种方式可以接受JobDetail传递的参数`:
public class QuartzJob2 extends QuartzJobBean {
private Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
logger.info("job1-start");
Utils.sleep(TimeUnit.SECONDS, 4);
// 获取参数
JobDataMap jobDataMap = jobExecutionContext.getMergedJobDataMap();
String date = jobDataMap.getString("date");
logger.info("参数:" + date);
logger.info("job1-end");
}
}
这样就把JobDetail和我们之前定义的QuartzJob关联起来了。
2、配置JobDetail
JobDetail可以使用MethodInvokingJobDetailFactoryBean或者JobDetailFactoryBean配置,配置工作需要在一个Spring配置类中完成,可以定义一个QuartzConfig配置类,首先看MethodInvokingJobDetailFactoryBean的使用:
@Configuration
public class QuartzConfig {
@Bean
public MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean() {
MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
// 指定任务类在IoC容器中的Bean名称
bean.setTargetBeanName("quartzJob");
// 指定要执行的方法名称
bean.setTargetMethod("hello");
return bean;
}
}
这样就把JobDetail和之前QuartzJob所定义的任务关联起来了,接下来看JobDetailFactoryBean:
@Configuration
public class QuartzConfig {
@Bean
public JobDetailFactoryBean jobDetailFactoryBean() {
JobDetailFactoryBean bean = new JobDetailFactoryBean();
// 指定任务类名称
bean.setJobClass(QuartzJob2.class);
// 准备参数
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("date", "2020-8-16");
// 传递参数
bean.setJobDataMap(jobDataMap);
return bean;
}
}
这样就把JobDetail和之前定义的QuartzJob2关联起来了,同时传递了参数。
3、配置Trigger
Trigger同样定义在QuartzConfig配置类里,常用的Trigger有SimpleTrigger、CronTrigger等,它们分别可以通过SimpleTriggerFactoryBean、CronTriggerFactoryBean来完成配置,我们先用SimpleTriggerFactoryBean配置的触发器关联MethodInvokingJobDetailFactoryBean配置的JobDetail:
@Configuration
public class QuartzConfig {
@Bean
public SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
bean.setRepeatCount(10);
bean.setRepeatInterval(2000);
// 关联JobDetail
bean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());
return bean;
}
}
SimpleTriggerFactoryBean的用法比较简单
再用CronTriggerFactoryBean配置的触发器关联JobDetailFactoryBean配置的JobDetail:
@Configuration
public class QuartzConfig {
@Bean
public CronTriggerFactoryBean cronTriggerFactoryBean() {
CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
bean.setCronExpression("0/2 * * * * ? 2020");
// 关联JobDetail
bean.setJobDetail(jobDetailFactoryBean().getObject());
return bean;
}
}
CronTriggerFactoryBean可以实现类似Spring中@Scheduled的cron表达式的功能,同时支持了年份的配置。
4、配置Scheduler
最后一步就是通过SchedulerFactoryBean来配置Scheduler,来注册Trigger
@Configuration
public class QuartzConfig {
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
// 注册两个Trigger
bean.setTriggers(simpleTriggerFactoryBean().getObject(), cronTriggerFactoryBean().getObject());
return bean;
}
}
Quartz会使用异步线程去执行定时任务,不会出现像@Scheduled中定时任务在同一线程同步执行的情况。
到这里Spring Boot集成Quartz整个流程就完成了,运行项目就可以看到定时任务按照预期的效果执行。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70010293/viewspace-2885324/ ,如需转载,请注明出处,否则将追究法律责任。