java定时任务
本文介绍了Java实现定时任务的三种方式:Timer、ScheduledExecutorService和分布式框架。Timer是单线程串行执行任务,存在阻塞和异常风险;ScheduledExecutorService基于线程池,支持并行调度任务,提供了schedule(一次性任务)、scheduleAtFixedRate(固定速率)和scheduleWithFixedDelay(固定延迟)等方法,
在一个具体的时间节点实现任务。
- 单机定时任务
- 分布式定时任务:需要专门的调度框架,通过分布式锁、注册中心、任务分发机制等实现协调,解决重复执行
- 分布式框架定时任务
一.Timer+TimerTask:Timer就是一个while在循环执行run()
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
public static void main(String[] args) {
Timer timer = new Timer();
// 定义任务
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("执行任务,时间:" + System.currentTimeMillis());
}
};
// 延迟 2 秒后执行,每隔 3 秒执行一次
timer.schedule(task, 2000, 3000);
}
}
-
同一个 Timer → 串行执行任务(单线程)。
-
不同的 Timer → 各自独立线程,可以并行。
❌ 缺点
单线程:所有任务都在一个线程中运行,如果一个任务耗时过长,会阻塞后续任务。
异常风险:如果某个任务抛出未捕获异常,整个 Timer 线程会挂掉,所有任务停止。
不适合并发场景:无法充分利用多核 CPU。
二.ScheduledExecutorService:延迟队列 + 线程池
类层次结构
ScheduledExecutorService(接口)
ScheduledThreadPoolExecutor(实现类)继承自 ThreadPoolExecutor
内部基于 延迟队列 (DelayedWorkQueue) 管理任务
👉 所以它的本质就是一个 带调度能力的线程池。
基于 线程池,可以并行调度多个任务,避免了单线程阻塞的问题。
-
schedule:往延迟队列里放一个任务,线程池线程在 delay 到时后取出并执行一次。
-
scheduleAtFixedRate:计算下一次的绝对触发时间 = 上次计划时间 + period,到了时间就往队列丢任务,慢了就会堆积/追赶。
-
scheduleWithFixedDelay:等任务真正执行完,再计算下一次触发时间 = 当前完成时间 + delay,所以节奏稳定,不会堆积。
import java.util.concurrent.*;
public class ScheduledExecutorExample {
public static void main(String[] args) {
// 创建一个带 2 个线程的调度线程池
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
System.out.println("程序开始时间:" + System.currentTimeMillis());
// 1. 一次性延迟任务
scheduler.schedule(() -> {
System.out.println("一次性延迟任务执行时间:" + System.currentTimeMillis());
}, 3, TimeUnit.SECONDS);
// 2. 固定速率任务(任务耗时 2 秒,周期 1 秒)
scheduler.scheduleAtFixedRate(() -> {
long start = System.currentTimeMillis();
System.out.println("固定速率任务开始:" + start);
try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
long end = System.currentTimeMillis();
System.out.println("固定速率任务结束:" + end);
}, 1, 1, TimeUnit.SECONDS);
// 3. 固定延迟任务(任务耗时 2 秒,延迟 1 秒)
scheduler.scheduleWithFixedDelay(() -> {
long start = System.currentTimeMillis();
System.out.println("固定延迟任务开始:" + start);
try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
long end = System.currentTimeMillis();
System.out.println("固定延迟任务结束:" + end);
}, 1, 1, TimeUnit.SECONDS);
// 为了演示,这里设置 15 秒后关闭线程池
scheduler.schedule(() -> {
System.out.println("关闭调度器:" + System.currentTimeMillis());
scheduler.shutdown();
}, 15, TimeUnit.SECONDS);
}
}
一次性任务:Future.get() 可以拿到结果(Callable 返回值,Runnable 返回 null)。
周期任务:返回的 ScheduledFuture 主要用于 取消任务,不能用来拿结果。
所以:
想要结果 → 用一次性任务 + Callable。
想要周期性调度 → 返回值一般只用来 cancel。
一次性:
ScheduledFuture<?> schedule(Callable<V> callable, long delay, TimeUnit unit)
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
如果你用 Callable,任务会返回一个结果;
如果是 Runnable,返回值是 null。
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
ScheduledFuture<Integer> future = scheduler.schedule(() -> {
return 42;
}, 2, TimeUnit.SECONDS);
System.out.println("任务结果: " + future.get()); // 2秒后打印 42
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(() -> {
System.out.println("周期任务执行:" + System.currentTimeMillis());
}, 1, 1, TimeUnit.SECONDS);
// 运行 5 秒后取消任务
scheduler.schedule(() -> {
System.out.println("取消周期任务");
future.cancel(false); // false 表示不打断正在执行的任务,等当前任务自己结束后不再执行新任务了
}, 5, TimeUnit.SECONDS);
ScheduledExecutorService 的核心确实是 “延迟队列 + 线程池” 的组合
任务提交:任务被包装为具有延迟属性的ScheduledFutureTask,然后放入延迟队列中,队列会按照执行时间排序。
线程池的工作线程会从延迟队列中获取到已到期的任务(如果没有已到期任务,线程会阻塞等待,直到被唤醒)
当线程池没有工作线程时,已到期的延迟任务会加入到线程池的等待队列。
对于周期性任务,执行完毕后会重新计算下次执行时间然后放入延迟队列。
Spring的定时任务:Spring 的定时任务功能(@Scheduled 注解等)本质上是对 Java 原生 ScheduledExecutorService 的封装和扩展
@Scheduled 注解本身不直接支持分布式场景
在 Spring 配置类上添加@EnableScheduling注解,启用定时任务功能。
创建任务类,使用@Scheduled注解定义定时任务的执行规则。
线程池配置:
```java
@Configuration
@EnableScheduling // 启用定时任务
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
// 创建调度线程池实例
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
// 核心线程池大小(并发执行的任务数量)
scheduler.setPoolSize(5);
// 线程名称前缀(便于日志跟踪)
scheduler.setThreadNamePrefix("scheduled-task-");
// 任务执行超时时间(毫秒),超过此时间未执行的任务会被中断
scheduler.setAwaitTerminationSeconds(60);
// 当调度器关闭时,是否等待所有任务执行完成
scheduler.setWaitForTasksToCompleteOnShutdown(true);
// 任务拒绝策略(当线程池满时如何处理新任务)
scheduler.setRejectedExecutionHandler((Runnable r, java.util.concurrent.ThreadPoolExecutor executor) -> {
// 示例:记录任务拒绝日志
System.err.println("任务被拒绝执行: " + r.toString() + ",当前线程池状态: " + executor.getActiveCount() + "活跃线程/" + executor.getPoolSize() + "总线程");
});
// 初始化线程池
scheduler.initialize();
return scheduler;
}
}
配置TreadPoolTaskScheduler的Bean,然后会自动注册到spring中
@Configuration
@EnableScheduling
public class SchedulerConfig {
// 第一个调度器
@Bean(name = "scheduler1")
public ThreadPoolTaskScheduler scheduler1() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(3);
scheduler.setThreadNamePrefix("scheduler-1-");
return scheduler;
}
// 第二个调度器
@Bean(name = "scheduler2")
public ThreadPoolTaskScheduler scheduler2() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(2);
scheduler.setThreadNamePrefix("scheduler-2-");
return scheduler;
}
}
可以创建多个调度器,然后将任务加入到不同调度器。
当然,通常是增大单个调度器的线程大小
// 使用第一个调度器
@Scheduled(fixedRate = 1000, scheduler = "scheduler1")
public void task1() { ... }
// 使用第二个调度器
@Scheduled(fixedRate = 2000, scheduler = "scheduler2")
public void task2() { ... }
常用配置
@Component
public class ScheduledExamples {
// 1. 固定速率执行:每5秒执行一次(以上一次开始时间计算)
@Scheduled(fixedRate = 5000)
public void fixedRateTask() {
System.out.println("固定速率任务 - " + LocalDateTime.now());
}
// 2. 固定延迟执行:上一次完成后隔3秒执行
@Scheduled(fixedDelay = 3000)
public void fixedDelayTask() {
System.out.println("固定延迟任务 - " + LocalDateTime.now());
}
// 3. 初始延迟+固定速率:启动后延迟2秒,之后每4秒执行
@Scheduled(initialDelay = 2000, fixedRate = 4000)
public void initialDelayTask() {
System.out.println("初始延迟任务 - " + LocalDateTime.now());
}
// 4. Cron表达式:每天10:30执行
@Scheduled(cron = "0 30 10 * *?")
public void dailyCronTask() {
System.out.println("每日10:30任务 - " + LocalDateTime.now());
}
// 5. Cron表达式:每30秒执行一次
@Scheduled(cron = "0/30 * * * *?")
public void intervalCronTask() {
System.out.println("每30秒任务 - " + LocalDateTime.now());
}
// 6. 时间单位配置(Spring 5.3+):每2分钟执行一次
@Scheduled(fixedRate = 2, timeUnit = TimeUnit.MINUTES)
public void timeUnitTask() {
System.out.println("每2分钟任务 - " + LocalDateTime.now());
}
// 7. 指定调度器(需提前配置名为"specialScheduler"的线程池)
@Scheduled(fixedRate = 10000, scheduler = "specialScheduler")
public void specifiedSchedulerTask() {
System.out.println("指定调度器任务 - " + LocalDateTime.now());
}
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)