五分钟搞定,实现定时任务的五种方案!

开发 前端
任务调度是指基于给定的时间点,给定的时间间隔或者给定执行次数自动得执行任务。任务调度是是操作系统的重要组成部分,而对于实时的操作系统,任务调度直接影响着操作系统的实时性能。

我们在实际开发中,多多少少都会用到定时任务来处理一些问题。

比如金融项目中的对账,每天定时对昨天的账务进行核对,每个月初对上个月的账务进行核对等。

还比如,我们需要处理一些老数据迁移,修复一些新项目和老项目数据不兼容的问题等等。

常规实现方案

方案1:Timer

这个目前在项目中用得较少,直接贴demo代码。

具体的介绍可以查看api ,但是在某些框架中是有用到。

public class TestTimer {

public static void main(String[] args){

TimerTask timerTask = new TimerTask() {
@Override
public void run(){
System.out.println("task run:"+ new Date());
}
};

Timer timer = new Timer();
//安排指定的任务在指定的时间开始进行重复的固定延迟执行。这里是每3秒执行一次
timer.schedule(timerTask,10,3000);
}
}

执行结果:

task  run:Sun Dec 11 21:23:47 CST 2022
task run:Sun Dec 11 21:23:50 CST 2022
task run:Sun Dec 11 21:23:53 CST 2022

这么使用,阿里代码检查插件会提示:

图片

从提示中可以看出,在多线程并行处理定时任务时,Timer运行多个TimerTask时,只要有其中之一没有捕获抛出的异常,其他任务会自动终止运行。

方案2:ScheduledExecutorService

和Timer类型,也就是阿里代码检查插件推荐的方案:

public class TestScheduledExecutorService {

public static void main(String[] args){

ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
// 参数:1、任务体 2、首次执行的延时时间
// 3、任务执行间隔 4、间隔时间单位
service.scheduleAtFixedRate(()->System.out.println("task ScheduledExecutorService "+new Date()), 0, 3, TimeUnit.SECONDS);
}
}

运行结果:

task ScheduledExecutorService Sun Dec 11 21:30:06 CST 2022
task ScheduledExecutorService Sun Dec 11 21:30:09 CST 2022
task ScheduledExecutorService Sun Dec 11 21:30:12 CST 2022

阿里代码检查插件也会提示:

图片

这里提示的是我们创建线程池的方式,建议我们使用手动创建线程池,不要使用​​Executors​​工厂类,因为手动创建更能有效规划资源的使用。

方案3:spring task

用起来也非常简单:

@Slf4j
@Component
public class ScheduledService {

@Scheduled(cron = "0/5 * * * * *")
public void scheduled(){
log.info("=====>>>>>使用cron {}",System.currentTimeMillis());
}

@Scheduled(fixedRate = 5000)
public void scheduled1(){
log.info("=====>>>>>使用fixedRate{}", System.currentTimeMillis());
}

@Scheduled(fixedDelay = 5000)
public void scheduled2(){
log.info("=====>>>>>fixedDelay{}",System.currentTimeMillis());
}

}

运行结果:

2022-12-11 21:36:25.001  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>使用cron  1670765785001
2022-12-11 21:36:28.212 INFO 10660 --- [ scheduling-1] com.tian.utils.ScheduledService : =====>>>>>使用fixedRate1670765788212
2022-12-11 21:36:28.212 INFO 10660 --- [ scheduling-1] com.tian.utils.ScheduledService : =====>>>>>fixedDelay1670765788212
2022-12-11 21:36:30.001 INFO 10660 --- [ scheduling-1] com.tian.utils.ScheduledService : =====>>>>>使用cron 1670765790001
2022-12-11 21:36:33.212 INFO 10660 --- [ scheduling-1] com.tian.utils.ScheduledService : =====>>>>>使用fixedRate1670765793212
2022-12-11 21:36:33.213 INFO 10660 --- [ scheduling-1] com.tian.utils.ScheduledService : =====>>>>>fixedDelay1670765793213
2022-12-11 21:36:35.001 INFO 10660 --- [ scheduling-1] com.tian.utils.ScheduledService : =====>>>>>使用cron 1670765795001
2022-12-11 21:36:38.214 INFO 10660 --- [ scheduling-1] com.tian.utils.ScheduledService : =====>>>>>使用fixedRate1670765798214
2022-12-11 21:36:38.214 INFO 10660 --- [ scheduling-1] com.tian.utils.ScheduledService : =====>>>>>fixedDelay1670765798214
2022-12-11 21:36:40.001 INFO 10660 --- [ scheduling-1] com.tian.utils.ScheduledService : =====>>>>>使用cron 1670765800001
2022-12-11 21:36:43.214 INFO 10660 --- [ scheduling-1] com.tian.utils.ScheduledService : =====>>>>>使用fixedRate1670765803214
2022-12-11 21:36:43.215 INFO 10660 --- [ scheduling-1] com.tian.utils.ScheduledService : =====>>>>>fixedDelay1670765803215

方案4:多线程执行

基于注解设定多线程定时任务 :

@Component
@EnableScheduling // 1.开启定时任务
@EnableAsync // 2.开启多线程
public class MultithreadScheduleTask {

@Async
@Scheduled(fixedDelay = 5000) //间隔5秒
public void first() throws InterruptedException {
System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
Thread.sleep(1000 * 10);
}

@Async
@Scheduled(fixedDelay = 5000)
public void second(){
System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
}
}

运行结果:

第一个定时任务开始 : 21:44:02.800
线程 : 入库操作日志记录表 线程1

第二个定时任务开始 : 21:44:02.801
线程 : 入库操作日志记录表 线程2

第一个定时任务开始 : 21:44:07.801
线程 : 入库操作日志记录表 线程3

第二个定时任务开始 : 21:44:07.802
线程 : 入库操作日志记录表 线程4

第一个定时任务开始 : 21:44:12.807
线程 : 入库操作日志记录表 线程5

第二个定时任务开始 : 21:44:12.812
线程 : 入库操作日志记录表 线程6
......

方案5:quartz

我们需要引入依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

实现类:

public class Myquartz extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("这是我的 quartz 定时任务");
}
}

配置类:

/**
* @author tianwc 公众号:java后端技术全栈、面试专栏
* @version 1.0.0
* @date 2022年12月11日 21:48
*/
@Configuration
public class QuartzConfig {

@Bean
public JobDetail teatQuartzDetail(){
return JobBuilder.newJob(MyQuartz.class).withIdentity("myQuartz").storeDurably().build();
}

@Bean
public Trigger testQuartzTrigger(){
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()

.withIntervalInSeconds(10) //设置时间周期单位秒
.repeatForever();

return TriggerBuilder.newTrigger().forJob(teatQuartzDetail())

.withIdentity("testQuartz")
.withSchedule(scheduleBuilder)
.build();
}
}

只要启动Spring Boot项目,就会输出:

这是我的 quartz 定时任务
这是我的 quartz 定时任务
这是我的 quartz 定时任务

其他方案

我们在项目,可能会涉及动态调整定时任务执行core表达式、动态关闭开启定时任务,我们可以使用​​SchedulingConfigurer​​来实现(使用数据库结合来搞):

比如:

@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
@Autowired
private ApplicationContext context;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar){
for (SpringScheduledCron springScheduledCron : cronRepository.findAll()) {
Class<?> clazz;
Object task;
try {
clazz = Class.forName(springScheduledCron.getCronKey());
task = context.getBean(clazz);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("spring_scheduled_cron表数据" + springScheduledCron.getCronKey() + "有误", e);
} catch (BeansException e) {
throw new IllegalArgumentException(springScheduledCron.getCronKey() + "未纳入到spring管理", e);
}
Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定时任务类必须实现ScheduledOfTask接口");
// 可以通过改变数据库数据进而实现动态改变执行周期
taskRegistrar.addTriggerTask(((Runnable) task),
triggerContext -> {
//这个可以使用持久层,比如Mybatis来实现,从数据库中获取
String cronExpression = "0/10 * * * * ? "
return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
}
);
}
}
@Bean
public Executor taskExecutor(){
return Executors.newScheduledThreadPool(10);
}
}

如果项目中用得到类似的,可以网上搜搜SchedulingConfigurer便可实现。

进而再扩展,那就来到分布式任务调度了。

什么是分布式任务调度?

任务调度是指基于给定的时间点,给定的时间间隔或者给定执行次数自动得执行任务。任务调度是是操作系统的重要组成部分,而对于实时的操作系统,任务调度直接影响着操作系统的实时性能。任务调度涉及到多线程并发、运行时间规则定制及解析、线程池的维护等诸多方面的工作。

WEB服务器在接受请求时,会创建一个新的线程服务。但是资源有限,必须对资源进行控制,首先就是限制服务线程的最大数目,其次考虑以线程池共享服务的线程资源,降低频繁创建、销毁线程的消耗;然后任务调度信息的存储包括运行次数、调度规则以及运行数据等。一个合适的任务调度框架对于项目的整体性能来说显得尤为重要。

分布式任务调度框架有:cronsun、Elastic-job、saturn、lts、TBSchedule、xxl-job 等。

另外,就是cron表达式,推荐 http://www.pppet.net/

图片

可以根据自己业务情况来,手动选择,自动生成表达式。

责任编辑:武晓燕 来源: Java后端技术全栈
相关推荐

2021-12-01 06:50:50

Docker底层原理

2024-01-31 08:38:57

Python定时任务函数

2021-11-22 12:35:40

Python命令定时任务

2017-09-27 11:00:50

LinuxBash使用技巧

2009-11-16 09:53:56

PHP上传类

2015-12-03 14:10:26

systemd容器Linux

2020-02-21 19:54:09

HTTPS 配置手把手教

2020-12-07 09:01:58

幂等系统f(f(x)) =f(

2023-04-04 09:13:15

2020-06-16 08:47:53

磁盘

2009-11-16 10:53:30

Oracle Hint

2009-10-21 18:19:36

VB.NET实现拖放

2024-01-22 08:53:00

策略任务RocketMQ

2009-11-17 14:50:50

Oracle调优

2022-12-16 09:55:50

网络架构OSI

2023-09-07 23:52:50

Flink代码

2023-07-23 18:47:59

Docker开源

2019-08-09 10:33:36

开发技能代码

2021-06-07 09:51:22

原型模式序列化

2009-11-05 14:53:54

Visual Stud
点赞
收藏

51CTO技术栈公众号