一、定时器是什么?
核心定义:定时器是多线程编程中常用的工具,本质是一种「延时执行任务」的机制 —— 允许程序指定某个任务在特定时间后执行,或周期性重复执行。
应用场景
- 定时备份数据(如每小时备份数据库)
- 定时发送心跳包(网络通信中维持连接)
- 延时执行操作(如用户注册后 5 分钟发送验证邮件)
- 周期性任务(如每隔 30 秒刷新系统状态)
核心特点
- 异步性:定时器任务的执行不阻塞主线程,独立在后台线程运行
- 时效性:严格按照预设时间触发(允许轻微误差,受系统调度影响)
- 独立性:多个定时器任务可并行执行,互不干扰
二、标准库中的定时器(Java 示例)
在 Java 中,JDK 提供了两种常用的定时器实现:java.util.Timer(基础版)和ScheduledExecutorService(推荐,线程池实现,更稳定)。
2.1 Timer 类(简单场景使用)
核心 API:
- schedule(TimerTask task, long delay):延迟delay毫秒后执行一次任务
- schedule(TimerTask task, long delay, long period):延迟delay毫秒后,每隔period毫秒重复执行
- cancel():取消定时器,终止所有任务
代码示例:
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
public static void main(String[] args) {
Timer timer = new Timer();
// 1. 延迟2秒执行一次任务
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("延迟2秒执行的单次任务");
}
}, 2000);
// 2. 延迟1秒后,每隔3秒重复执行任务
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("周期性任务:每隔3秒执行一次");
System.out.println("当前线程:" + Thread.currentThread().getName()); // 输出 Timer-0
}
}, 1000, 3000);
// 主线程继续执行,不阻塞
System.out.println("主线程执行完毕,定时器在后台运行");
}
}
2.2 Timer 的局限性(重要!)
- 单线程问题:Timer 内部只有一个工作线程,所有任务串行执行。如果某个任务执行时间过长,会阻塞后续任务(例如任务 A 执行了 10 秒,任务 B 本应在 3 秒后执行,实际会延迟到 10 秒后)。
- 异常崩溃问题:若某个任务抛出未捕获异常,Timer 的工作线程会直接终止,后续所有任务都无法执行。
- 时间漂移:周期性任务的执行时间是「上一次任务结束时间 + 周期」,而非「上一次任务开始时间 + 周期」,长期运行会导致时间漂移。
2.3 推荐方案:ScheduledExecutorService(线程池实现)
ScheduledExecutorService是 JDK 5 + 引入的,基于线程池,解决了 Timer 的所有缺陷,是生产环境的首选。
核心 API:
- schedule(Runnable command, long delay, TimeUnit unit):延迟执行单次任务
- scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):固定频率执行(以上一次任务开始时间为基准)
- scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit):固定延迟执行(以上一次任务结束时间为基准)
代码示例:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorDemo {
public static void main(String[] args) {
// 创建包含2个核心线程的定时线程池
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// 1. 延迟2秒执行单次任务
executor.schedule(() -> {
System.out.println("ScheduledExecutor:延迟2秒的单次任务");
}, 2, TimeUnit.SECONDS);
// 2. 固定频率:延迟1秒后,每隔3秒执行一次(不受任务执行时间影响)
executor.scheduleAtFixedRate(() -> {
System.out.println("固定频率任务:每隔3秒执行(以上一次开始时间为准)");
try {
Thread.sleep(1000); // 模拟任务执行1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, 3, TimeUnit.SECONDS);
// 3. 固定延迟:延迟1秒后,上一次任务结束后隔2秒执行
executor.scheduleWithFixedDelay(() -> {
System.out.println("固定延迟任务:上一次结束后隔2秒执行");
try {
Thread.sleep(1000); // 模拟任务执行1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, 2, TimeUnit.SECONDS);
// 注意:主线程结束后,线程池不会自动关闭,需手动 shutdown
// executor.shutdown(); // 若需主线程结束后终止定时器,可调用
}
}
2.4 两者对比
|
特性 |
Timer |
ScheduledExecutorService |
|
线程模型 |
单线程 |
多线程(线程池) |
|
异常处理 |
单个任务异常导致整体崩溃 |
单个任务异常不影响其他任务 |
|
周期性任务计时 |
以上一次结束时间为准 |
支持固定频率 / 固定延迟两种模式 |
|
稳定性 |
低 |
高(生产环境推荐) |
三、手动实现一个简单定时器(加深理解)
为了更好地掌握定时器的核心原理,我们手动实现一个基础版本。核心思路:
- 用「优先级队列」存储待执行任务(按执行时间排序,保证每次取最早要执行的任务)
- 用一个后台线程循环检查队列,若任务到达执行时间则执行
- 若队列中无任务或任务未到执行时间,线程阻塞(避免空轮询浪费 CPU)
3.1 核心代码实现
import java.util.PriorityQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 手动实现简单定时器
*/
public class MyTimer {
// 1. 任务类:存储任务内容和执行时间(绝对时间:System.currentTimeMillis() + 延迟时间)
static class Task implements Comparable<Task> {
Runnable runnable; // 要执行的任务
long executeTime; // 执行时间(毫秒级时间戳)
public Task(Runnable runnable, long delay) {
this.runnable = runnable;
this.executeTime = System.currentTimeMillis() + delay;
}
// 优先级队列排序规则:执行时间早的任务排在前面
@Override
public int compareTo(Task o) {
return (int) (this.executeTime – o.executeTime);
}
}
// 2. 优先级队列:存储任务(线程安全,需加锁)
private final PriorityQueue<Task> taskQueue = new PriorityQueue<>();
// 3. 锁和条件变量:保证队列操作线程安全,实现线程阻塞/唤醒
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
// 4. 控制定时器是否运行
private volatile boolean isRunning = true;
// 启动定时器后台线程
public MyTimer() {
new Thread(this::loop, "MyTimer-Worker").start();
}
// 循环检查并执行任务
private void loop() {
while (isRunning) {
lock.lock();
try {
// 若队列无任务,阻塞等待
while (taskQueue.isEmpty()) {
condition.await();
}
// 取队列中最早要执行的任务
Task task = taskQueue.peek();
long now = System.currentTimeMillis();
if (now >= task.executeTime) {
// 任务到达执行时间,执行并从队列移除
taskQueue.poll();
task.runnable.run();
} else {
// 任务未到执行时间,阻塞到执行时间(避免空轮询)
condition.await(task.executeTime – now, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException e) {
e.printStackTrace();
break;
} finally {
lock.unlock();
}
}
}
// 添加任务(延迟delay毫秒执行)
public void schedule(Runnable runnable, long delay) {
lock.lock();
try {
taskQueue.add(new Task(runnable, delay));
condition.signal(); // 唤醒阻塞的工作线程(新任务可能是最早执行的)
} finally {
lock.unlock();
}
}
// 停止定时器
public void stop() {
isRunning = false;
lock.lock();
try {
condition.signal(); // 唤醒工作线程,让其退出循环
} finally {
lock.unlock();
}
}
// 测试
public static void main(String[] args) {
MyTimer timer = new MyTimer();
// 添加任务1:延迟1秒执行
timer.schedule(() -> {
System.out.println("自定义定时器:延迟1秒执行的任务");
System.out.println("执行线程:" + Thread.currentThread().getName()); // MyTimer-Worker
}, 1000);
// 添加任务2:延迟3秒执行
timer.schedule(() -> {
System.out.println("自定义定时器:延迟3秒执行的任务");
timer.stop(); // 执行完毕后停止定时器
}, 3000);
System.out.println("主线程继续执行");
}
}
3.2 实现要点解析
- 线程安全:优先级队列PriorityQueue是非线程安全的,因此用ReentrantLock保证队列操作(添加、取出)的原子性。
- 阻塞机制:用Condition的await()实现线程阻塞,避免空轮询(若任务未到执行时间,线程阻塞到执行时间,节省 CPU 资源)。
- 任务排序:Task实现Comparable接口,优先级队列按执行时间排序,保证每次取出最早要执行的任务。
3.3 局限性(待优化)
- 单线程执行:所有任务串行,若某个任务执行时间过长,会阻塞后续任务(可通过线程池优化,让任务在不同线程执行)。
- 不支持周期性任务:当前仅支持单次延迟执行(可扩展Task类,添加周期参数,执行后重新计算下次执行时间并放回队列)。
- 无异常处理:若任务抛出异常,会导致定时器线程终止(可在task.runnable.run()外层加try-catch捕获异常)。
四、总结
- 定时器的核心是「延时 / 周期性执行任务」,本质是多线程异步调度的应用。
- 实际开发中,优先使用ScheduledExecutorService(线程池实现),避免使用Timer(单线程、异常崩溃问题)。
- 手动实现定时器的核心思路:优先级队列(任务排序)+ 线程阻塞(避免空轮询)+ 线程安全(锁机制)。
- 进阶方向:可基于手动实现的版本,扩展线程池、周期性任务、异常重试等功能,更贴近生产环境需求。





