在实际的 Java 开发中,定时任务的需求非常普遍。例如,你需要定期清理缓存、轮询数据库状态、或者在特定时间发送邮件。java.util.Timer 类就是 Java 提供的实现定时任务的基础工具。本文将深入剖析 Timer 类的源码,并结合实际应用场景,分享一些使用 Timer 的技巧和避坑经验。
问题场景:任务延迟执行与重复执行
假设我们需要每隔 5 秒执行一次某个任务,并设置一定的延迟。初学者可能很快写出类似下面的代码:
import java.util.Timer;
import java.util.TimerTask;
public class TimerExample {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Task executed at: " + System.currentTimeMillis());
}
}, 2000, 5000); // 延迟 2 秒,每隔 5 秒执行
}
}
这段代码看似简单,但在高并发环境下,或者任务执行时间超过预期间隔时,就可能出现问题。例如,如果任务执行时间超过 5 秒,下一次任务的执行时间就会被延迟,导致任务积压。
Timer 源码剖析:单线程模型与任务调度
要理解 Timer 的行为,我们需要深入了解它的源码。Timer 的核心是单线程模型。它内部维护一个 TaskQueue,用于存储待执行的 TimerTask。TimerThread 是一个守护线程,负责从 TaskQueue 中取出任务并执行。
// 核心字段
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);
// schedule 方法的关键逻辑
synchronized void sched(TimerTask task, long time) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time.");
// 线程安全检查
synchronized(queue) {
if (!thread.newTasksMayBeScheduled)
throw new IllegalStateException("Timer already cancelled.");
queue.add(task);
if (queue.getMin() == task)
queue.notify(); // 唤醒 TimerThread
}
}
从源码可以看出,Timer 使用 synchronized 关键字来保证线程安全,但是也引入了单线程的瓶颈。如果任务执行时间过长,会阻塞后续任务的执行。
实战避坑:高并发场景下的替代方案
在高并发场景下,Timer 可能会成为性能瓶颈。这时,我们可以考虑使用以下替代方案:
ScheduledExecutorService: 这是 Java 提供的更强大的定时任务调度器。它基于线程池,可以并发执行多个任务,避免了单线程的瓶颈。

ScheduledExecutorService executor = Executors.newScheduledThreadPool(5); // 创建一个包含 5 个线程的线程池 executor.scheduleAtFixedRate(() -> { System.out.println("Task executed at: " + System.currentTimeMillis()); }, 2, 5, TimeUnit.SECONDS); // 延迟 2 秒,每隔 5 秒执行Quartz: 这是一个功能更全面的开源任务调度框架。它提供了丰富的任务调度策略,例如 cron 表达式、任务持久化等。适用于复杂的定时任务需求。
Spring Task: 如果你使用 Spring 框架,可以使用 Spring Task 提供的
@Scheduled注解来简化定时任务的配置。
@Scheduled(fixedRate = 5000) // 每隔 5 秒执行 public void myTask() { System.out.println("Task executed at: " + System.currentTimeMillis()); }
在实际项目中,选择合适的定时任务方案需要根据具体的业务需求和并发量来决定。
补充:Nginx 反向代理和负载均衡下的定时任务
如果你的应用部署在 Nginx 反向代理和负载均衡集群中,需要特别注意定时任务的执行。为了避免重复执行任务,你需要确保只有一个节点执行定时任务。可以使用以下方法:
- 分布式锁: 使用 Redis 或 ZooKeeper 等分布式锁服务,保证只有一个节点能够获取到锁并执行定时任务。
- 指定节点执行: 在 Nginx 配置中,将定时任务的请求路由到指定的节点。
选择哪种方案取决于你的应用架构和技术栈。
总结:选择合适的定时任务方案
java学习 中,Timer 是一个简单易用的定时任务工具,但在高并发场景下可能存在性能瓶颈。java学习 者应该根据实际需求选择合适的定时任务方案,例如 ScheduledExecutorService、Quartz 或 Spring Task。同时,在分布式环境下,需要考虑任务重复执行的问题,并采取相应的措施来避免。
冠军资讯
代码一只喵