Java中多种方式实现定时任务
文章目录
- Java中多种方式实现定时任务
- 一、Timer
- 1. 特点
- 2. 实现步骤
- 步骤一:创建定时任务类
- 步骤二:在主程序中使用Timer执行任务
- 3. 注意事项
- 4. 适用范围
- 二、ScheduledExecutorService
- 1. 特点
- 2. 实现步骤
- 步骤一:创建定时任务类
- 步骤二:在主程序中使用ScheduledExecutorService执行任务
- 3. 注意事项
- 4. 适用范围
- 三、@Scheduled
- 1. 特点
- 2. 实现步骤
- 步骤一:启用定时任务
- 步骤二:创建定时任务方法并使用 @Scheduled 注解
- 3. 注意事项
- 4. 适用范围
- 四、Quartz
- 特点
- 实现步骤
- 步骤一:引入依赖
- 步骤二:编写 Job 类
- 步骤三:配置和启动 Quartz Scheduler
- 注意事项
- 适用范围
- 五、Xxl Job
- 特点
- 实现步骤
- 步骤一:拉取镜像、创建容器(此处使用docker compose编排)
- 步骤二:在项目中配置
- 步骤三:编写任务
- 步骤四:登录管理端,完成管理配置
- 注意事项
- 适用范围
一、Timer
1. 特点
- 简单易用:
Timer
类提供了简单的定时任务调度功能,易于上手。 - 基于单线程:所有任务由单个后台线程按顺序执行,任务之间可能相互影响。
- 异常处理:未捕获的异常会导致整个
Timer
终止,后续任务不会执行。 - 精准度:在任务执行时间过长或频繁调度任务时,精确性可能受到影响。
2. 实现步骤
步骤一:创建定时任务类
import java.util.Timer;
import java.util.TimerTask;
public class MyTask extends TimerTask {
public void run() {
System.out.println("定时任务执行中...");
// 在这里定义定时任务的逻辑
}
}
步骤二:在主程序中使用Timer执行任务
public class Main {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new MyTask();
// 设定定时任务,延迟0毫秒后执行,每隔1000毫秒执行一次
timer.schedule(task, 0, 1000);
}
}
3. 注意事项
- 异常处理:务必在定时任务的执行体内部捕获所有可能的异常,防止未捕获的异常导致
Timer
停止运行。一个任务的异常不应该影响其他任务,所以需要确保异常被处理,不会影响Timer
的整体运行。 - 线程安全性:确保任务类是线程安全的,特别是如果任务共享了全局或静态变量时,需要考虑线程安全性。
- 长时间任务:避免单个任务执行时间过长,可能影响后续任务的调度。如果任务逻辑耗时较长,可以考虑异步执行或者拆分任务。
- Timer 定时器的局限性:
- 单线程执行:
Timer
是单线程执行任务的,一个任务的执行时间过长会影响后续任务的调度。 - 任务重复性:
Timer
不适合需要精确执行时间的场景,如果一个任务执行时间超出了间隔时间,可能会导致任务重叠。 - 异常处理:如果一个任务在执行过程中抛出未捕获的异常,整个
Timer
都会停止运行,后续任务也不会执行。
- Timer 取消与重新调度:
- 任务取消:考虑到任务的取消,如果需要取消定时任务,务必确保正确调用
Timer
的cancel()
方法。 - 任务重新调度:如果需要重新调度任务,需要小心使用
Timer
的schedule()
方法,避免不必要的问题。
- 性能和资源管理:
- 资源占用:
Timer
每个任务会创建一个线程,如果任务过多可能会导致资源占用过多的问题。 - 定时器关闭:及时关闭不再需要的
Timer
,以释放资源。
- 多个 Timer 对象:
- 避免滥用:避免创建过多的
Timer
对象,合理使用 Timer 对象,不要滥用它。
4. 适用范围
该实现方法适用于简单的定时任务需求,比如简单的周期性任务、定时提醒等。适用于不需要高精度和大规模任务处理的场景。如果需要更多的控制、更高精度或大规模任务处理,可能需要考虑其他方案,比如ScheduledExecutorService
等。
这种实现方式非常适合对定时任务有基本需求且任务量不大的情况。但要留意任务之间的相互影响和异常处理,以确保程序稳定运行。
二、ScheduledExecutorService
1. 特点
- 灵活性:相比于
Timer
,ScheduledExecutorService
提供了更灵活的调度策略和更好的异常处理机制。 - 并发性:
ScheduledExecutorService
基于线程池,能够更好地处理多个并发任务。 - 更精准的时间控制:提供了更高精度的任务调度能力。
- 更好的异常处理:异常不会导致整个调度器终止。
2. 实现步骤
步骤一:创建定时任务类
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class MyTask implements Runnable {
public void run() {
System.out.println("定时任务执行中...");
// 在这里定义定时任务的逻辑
}
}
步骤二:在主程序中使用ScheduledExecutorService执行任务
public class Main {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = new MyTask();
// 设定定时任务,延迟0秒后执行,每隔1秒执行一次
executor.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);
}
}
3. 注意事项
- 线程池大小选择:根据实际情况选择合适的线程池大小,避免资源浪费或线程不足的情况。
- 异常处理:务必在任务的执行体内部捕获所有可能的异常,防止未捕获的异常导致线程池的意外终止,影响其他任务的执行。
- 长时间任务:避免单个任务执行时间过长,可能影响后续任务的调度。确保任务逻辑精简高效,不要阻塞线程池中的其他任务。
- 延时任务与周期性任务设置:在设定延时任务和周期性任务时,确保设定的时间间隔和延迟时间正确无误,以避免任务执行的时间误差。
- 任务取消与重新调度:如果需要取消或重新调度任务,确保使用
ScheduledFuture
对象的cancel()
和schedule()
方法来正确操作任务的状态和重新安排。 - 线程池管理:合理管理线程池,不要滥用线程池资源。如果需要长时间运行的定时任务,需要合理调整线程池的大小。
4. 适用范围
ScheduledExecutorService
适用于需要更精细控制、更高并发性和更好异常处理的场景。特别适合于需要处理大量定时任务、精确时间控制或有较高并发需求的应用。对于更复杂的任务调度和管理,ScheduledExecutorService
提供了更多的灵活性和可控性。
这种实现方式对于需要更多控制和更高并发性能的场景非常有用。相比Timer
,ScheduledExecutorService
更适合处理大规模任务并且对任务执行精度和并发性有更高要求的情况。
三、@Scheduled
@Scheduled
是 Spring 框架中用于实现定时任务的注解。通过该注解,可以非常方便地在 Spring 应用中创建定时任务。
1. 特点
- Spring集成:
@Scheduled
是 Spring 框架提供的注解,可以轻松地与 Spring 项目集成。 - 简单配置:通过注解方式配置定时任务,简单直观。
- 多种调度策略:支持多种调度策略,如固定延迟、固定间隔等。
2. 实现步骤
步骤一:启用定时任务
在 Spring Boot 应用的配置类或带有 @EnableScheduling
注解的类中开启定时任务功能。
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableScheduling
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
步骤二:创建定时任务方法并使用 @Scheduled 注解
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyScheduledTask {
// 每隔5秒执行一次
@Scheduled(fixedRate = 5000)
public void myTask() {
System.out.println("定时任务执行中...");
// 在这里定义定时任务的逻辑
}
}
3. 注意事项
- 方法签名问题:被
@Scheduled
注解的方法不能有任何参数,否则注解可能会失效。确保定时任务方法无参数。 - 访问权限:被
@Scheduled
注解的方法不能是private
的,因为 Spring 需要通过反射调用这些方法,所以方法必须是公共可访问的(public
或protected
)。 - 异常处理:务必在任务方法内部进行异常处理,防止异常抛出导致定时任务停止运行。可以使用
try-catch
块来捕获异常并进行处理。 - 任务并发性:默认情况下,Spring 的定时任务是单线程执行的,如果一个任务执行时间过长,可能会影响后续任务的执行。确保任务逻辑简洁高效,避免长时间执行任务的情况。
- 固定延迟任务的注意事项:使用固定延迟(
fixedDelay
)时,如果前一个任务执行时间超出设定的延迟时间,可能会影响后续任务的调度。确保前一个任务的执行时间不会超过延迟时间。 - Cron 表达式:当使用 Cron 表达式时,务必仔细检查表达式的准确性,避免出现任务执行频率与预期不符的情况。确保表达式设置正确,符合预期执行的时间规则。
4. 适用范围
@Scheduled
注解适用于简单的定时任务场景,特别适合于 Spring 项目中需要简单定时任务调度的情况。不适用于需要更高级控制和灵活性的任务,比如复杂的任务依赖关系、大规模任务管理等。
这种方式非常适合于简单的定时任务需求,对于 Spring 项目而言是一种轻便易用的方式,但对于更复杂的任务管理和控制需求,可能需要使用其他调度方案来满足。
四、Quartz
Quartz 是一个强大的任务调度框架,能够在 Java 应用中实现灵活的定时任务调度。
特点
- 灵活的任务调度:支持多种触发器类型和灵活的任务调度策略。
- 集群支持:能够在分布式环境中部署,并实现任务的高可用性和负载均衡。
- 持久化支持:支持将任务调度信息持久化到数据库中,防止任务配置丢失。
实现步骤
步骤一:引入依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.3</version>
</dependency>
步骤二:编写 Job 类
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Quartz定时任务执行中...");
// 在这里定义定时任务的逻辑
}
}
步骤三:配置和启动 Quartz Scheduler
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzSchedulerExample {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("myJob", "group1")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever())
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
注意事项
- Job 类的线程安全性:确保 Job 类是线程安全的,因为 Quartz 可能会并发执行 Job 实例。
- 持久化配置:建议将任务调度信息配置持久化,以防止应用重启导致任务信息丢失。
适用范围
Quartz 适用于对任务调度有较高要求的场景,特别是需要复杂的任务调度逻辑、集群环境和持久化存储任务信息的情况。对于需要更高级调度功能和可靠性的应用,Quartz 是一个强大且可靠的选择。
五、Xxl Job
Xxl Job 是一个分布式任务调度平台,提供了简单易用的任务调度和管理功能,能够帮助企业实现任务调度和管理的需求。
特点
- 分布式任务调度:支持分布式任务调度,适用于多台服务器的任务管理。
- Web 管理界面:提供友好的 Web 管理界面,便于任务的配置和监控。
- 任务日志:支持任务执行日志记录和查看,方便排查问题。
- 任务流程监控:提供任务流程监控和报警功能。
实现步骤
步骤一:拉取镜像、创建容器(此处使用docker compose编排)
version: '2'
#自定义的docker网络
networks:
your_docker_net:
external: true
services:
xxl-job-compose:
#读取docker-compose/Dockerfile的位置
build: .
#镜像名称
image: xuxueli/xxl-job-admin:2.3.1
#容器名称
container_name: xxl_job
ports:
- "9898:8080"
#用到的数据库环境
environment:
PARAMS: '--spring.datasource.url=jdbc:mysql://192.168.16.128:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
--spring.datasource.username=root
--spring.datasource.password=123'
volumes:
- /usr/local/softwares/xxl_job/logs:/data/applogs
networks:
your_docker_net:
ipv4_address: 172.18.12.100
步骤二:在项目中配置
在项目中编写定时任务,然后通过 Xxl Job 平台进行管理和调度。
#pom文件导入依赖
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.3.1</version>
</dependency>
#application.yml
xxl:
job:
admin:
# 管理端页面地址
addresses: http://192.168.16.128:9898/xxl-job-admin
executor:
# 执行器名称
appname: xxl-job-executor-sample
# 执行器占用端口
port: 9777
# 执行器所在服务器的 IP 地址
ip: 192.168.200.132
# 执行器注册的访问令牌,用于身份验证
accessToken: default_token
//配置类,配置XxlJobSpringExecutor
package com.star.common.config;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class XxlJobConfig {
// Xxl Job Admin 地址
@Value("${xxl.job.admin.addresses}")
private String adminAddress;
// Xxl Job 执行器端口
@Value("${xxl.job.executor.port}")
private Integer port;
// Xxl Job 访问令牌
@Value("${xxl.job.accessToken}")
private String token;
// Xxl Job 执行器名称
@Value("${xxl.job.executor.appname}")
private String appName;
// 配置 XxlJobSpringExecutor 实例
@Bean
public XxlJobSpringExecutor xxlJobExecutor(){
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddress);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(token);
xxlJobSpringExecutor.setAppname(appName);
log.debug("xxl_job配置成功{}",xxlJobSpringExecutor);
return xxlJobSpringExecutor;
}
}
步骤三:编写任务
//编写任务
package com.star.controller;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class TheXxlJob {
// XxlJob 注解,定义任务名为 "test"
@XxlJob("test")
public void doJob() {
// 执行任务的逻辑,这里简单地输出一行日志和打印一条信息
System.out.println("xxl_job_test");
log.debug("success");
}
}
步骤四:登录管理端,完成管理配置
有可能会存在错误,可以查看日志完成。
执行器中如果自动注册不为本机IP地址,则手动录入改成本机IP地址
注意事项
- 平台配置:需要根据官方文档正确配置和启动 Xxl Job 平台。
- 任务管理:需要在 Xxl Job 平台进行任务的管理和配置,保证任务的正常运行。
适用范围
Xxl Job 适用于需要分布式任务调度和管理的企业应用。它提供了简单易用的管理界面和分布式任务调度能力,特别适合多服务器环境下的任务管理和调度。对于需要在分布式环境中管理大量任务的应用,Xxl Job 提供了一个可靠的解决方案。