Java中实现定时任务执行的方式总结
一、概述
关于在Java项目中实现定时任务执行的几种方式总结如下:
-
单进程实现定时任务主要有以下几种方式:
1.
Timer
和TimerTask
(JDK 原生)2.
ScheduledExecutorService
(JDK 1.5+,推荐)3.
Spring Task
(Spring 框架支持)4.
Quartz
(企业级任务调度框架)5. DelayQueue 阻塞队列
-
分布式定时任务
1、使用Redis来实现定时任务
2、使用xxl-job实现定时任务
二、详情
1. Timer 和 TimerTask(JDK 原生)
Java中的Timer类是一个定时调度器,用于在指定的时间点执行任务。Timer 类用于实现定时任务,最大的好处就是他的实现非常简单,特别的轻量级,因为它是Java内置的,所以只需要简单调用就行了。
public class MyTimerTask {public static void main(String[] args) {// 定义一个任务TimerTask timerTask = new TimerTask() {@Overridepublic void run() {System.out.println("Run timerTask:" + new Date());}};// 计时器Timer timer = new Timer();// 添加执行任务(延迟 1s 执行,每 3s 执行一次)timer.schedule(timerTask, 1000, 3000);}
}
优点 | 缺点 |
---|---|
|
|
2. ScheduledExecutorService(JDK 1.5+,推荐)
ScheduledExecutorService基于线程池(ThreadPoolExecutor),支持多任务并发执行。相比 Timer,更稳定、更灵活。
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);// 延迟 1s 后执行
scheduler.schedule(() -> {System.out.println("Task executed once at: " + new Date());
}, 1, TimeUnit.SECONDS);// 延迟 1s 后执行,之后每 2s 执行一次
scheduler.scheduleAtFixedRate(() -> {System.out.println("Fixed-rate task executed at: " + new Date());
}, 1, 2, TimeUnit.SECONDS);
优点 | 缺点 |
---|---|
|
|
3. Spring Task(Spring 框架支持)
适合 Spring/Spring Boot 项目,基于 ScheduledExecutorService,提供注解式任务调度。
@EnableScheduling
@SpringBootApplication
public class SpringbootApplication {public static void main(String[] args) {SpringApplication.run(SpringbootApplication.class, args);}}@Component
public class TimerTask {@Scheduled(cron = "0 0 0 0 7 *")public void task() {System.out.println("定时任务...");}}
优点 | 缺点 |
---|---|
| 只适合 Spring/Spring Boot 项目 |
4. Quartz(企业级任务调度框架)
Quartz是OpenSymphony开源组织在Job scheduling领域的一个Java实现的开源项目。
// 1. 定义 Job
public class MyJob implements Job {@Overridepublic void execute(JobExecutionContext context) {System.out.println("Quartz Job executed at: " + new Date());}
}// 2. 配置 Trigger 和 Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("myJob", "group1").build();Trigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5) // 每 5s 执行一次.repeatForever()).build();scheduler.scheduleJob(job, trigger);
scheduler.start();
优点 | 缺点 |
---|---|
|
|
5. DelayQueue 阻塞队列
DelayQueue是一个带有延迟时间的无界阻塞队列,它的元素必须实现Delayed接口。当从DelayQueue中取出一个元素时,如果其延迟时间还未到达,则会阻塞等待,直到延迟时间到达。因此,我们可以通过将任务封装成实现Delayed接口的元素,将其放入DelayQueue中,再使用一个线程不断地从DelayQueue中取出元素并执行任务,从而实现定时任务的调度。
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;public class DelayedTask implements Delayed {private final String taskName;private final long executeTime; // 执行时间(毫秒时间戳)public DelayedTask(String taskName, long delayMs) {this.taskName = taskName;this.executeTime = System.currentTimeMillis() + delayMs;}@Overridepublic long getDelay(TimeUnit unit) {// 返回剩余延迟时间long remainingDelay = executeTime - System.currentTimeMillis();return unit.convert(remainingDelay, TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed other) {// 按执行时间排序(早到期者优先)return Long.compare(this.executeTime, ((DelayedTask) other).executeTime);}public void execute() {System.out.println("Executing task: " + taskName + " at " + new Date(executeTime));}
}public class DelayQueueScheduler {private static final DelayQueue<DelayedTask> queue = new DelayQueue<>();public static void main(String[] args) throws InterruptedException {// 提交延迟任务queue.put(new DelayedTask("Task 1", 3000)); // 3s 后执行queue.put(new DelayedTask("Task 2", 1000)); // 1s 后执行queue.put(new DelayedTask("Task 3", 5000)); // 5s 后执行// 启动消费者线程处理任务while (!queue.isEmpty()) {DelayedTask task = queue.take(); // 阻塞直到有任务到期task.execute();}}
}
优点 | 缺点 |
---|---|
|
|
6、使用 Redis 实现延迟任务
使用 Redis 实现延迟任务的方法大体可分为两类:通过 ZSet 的方式和键空间通知的方式。
① ZSet 实现方式
通过 ZSet 实现定时任务的思路是,将定时任务存放到 ZSet 集合中,并且将过期时间存储到 ZSet 的 Score 字段中,然后通过一个无线循环来判断当前时间内是否有需要执行的定时任务,如果有则进行执行,具体实现代码如下:
import redis.clients.jedis.Jedis;
import utils.JedisUtils;
import java.time.Instant;
import java.util.Set;public class DelayQueueExample {// zset keyprivate static final String _KEY = "myTaskQueue";public static void main(String[] args) throws InterruptedException {Jedis jedis = JedisUtils.getJedis();// 30s 后执行long delayTime = Instant.now().plusSeconds(30).getEpochSecond();jedis.zadd(_KEY, delayTime, "order_1");// 继续添加测试数据jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");// 开启定时任务队列doDelayQueue(jedis);}/*** 定时任务队列消费* @param jedis Redis 客户端*/public static void doDelayQueue(Jedis jedis) throws InterruptedException {while (true) {// 当前时间Instant nowInstant = Instant.now();long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒时间long nowSecond = nowInstant.getEpochSecond();// 查询当前时间的所有任务Set<String> data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);for (String item : data) {// 消费任务System.out.println("消费:" + item);}// 删除已经执行的任务jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);Thread.sleep(1000); // 每秒查询一次}}
}
② 键空间通知
我们可以通过 Redis 的键空间通知来实现定时任务,它的实现思路是给所有的定时任务设置一个过期时间,等到了过期之后,我们通过订阅过期消息就能感知到定时任务需要被执行了,此时我们执行定时任务即可。
默认情况下 Redis 是不开启键空间通知的,需要我们通过 config set notify-keyspace-events Ex
的命令手动开启,开启之后定时任务的代码如下:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import utils.JedisUtils;public class TaskExample {public static final String _TOPIC = "__keyevent@0__:expired"; // 订阅频道名称public static void main(String[] args) {Jedis jedis = JedisUtils.getJedis();// 执行定时任务doTask(jedis);}/*** 订阅过期消息,执行定时任务* @param jedis Redis 客户端*/public static void doTask(Jedis jedis) {// 订阅过期消息jedis.psubscribe(new JedisPubSub() {@Overridepublic void onPMessage(String pattern, String channel, String message) {// 接收到消息,执行定时任务System.out.println("收到消息:" + message);}}, _TOPIC);}
}
7、使用xxl-job实现定时任务
xxl-job是一款分布式定时任务调度平台,可以实现各种类型的定时任务调度,如定时执行Java代码、调用HTTP接口、执行Shell脚本等。xxl-job采用分布式架构,支持集群部署,可以满足高并发、大数据量的任务调度需求。
三、开源项目
1、https://gitee.com/xuxueli0323/xxl-job
2、https://gitee.com/dromara/disjob
3、https://gitee.com/KFCFans/PowerJob
4、https://gitee.com/aizuda/snail-job
5、https://gitee.com/elasticjob/elastic-job