Java手写计时器是怎么跑起来的?优先队列 + 单工作线程 + wait/notify以及细节纠错
摘要 这段代码实现了一个简易定时器,核心机制是使用优先队列管理定时任务,并通过单工作线程监控执行时机。主要特点包括: 任务封装:MyTimerTask将Runnable任务与执行时间戳绑定 优先级调度:使用PriorityQueue确保最早到期的任务始终在队首 智能等待:工作线程通过wait(timeout)精确休眠到下一个任务的执行时间 线程安全:通过synchronized和wait/noti
手写计时器是怎么跑起来的:优先队列 + 单工作线程 + wait/notify
这段代码的核心思想非常“工程化”:把未来要执行的任务按时间排序存起来,然后开一个专门的线程盯着“下一件该做的事”,时间没到就睡觉,时间到了就执行。
源代码(已纠错版)
package thread;
//基于抽象类的方式定义MyTimerTask
//abstract class MyTimerTask implements Runnable{
// @Override
// public abstract void run();
//}
import java.util.PriorityQueue;
import java.util.Timer;
import java.util.TimerTask;
class MyTimerTask implements Comparable<MyTimerTask>{
private Runnable task;
//记录任务要执行的时刻
private long time;
public MyTimerTask(Runnable task,long time){
this.task = task;
this.time = time;
}
public int compareTo(MyTimerTask o){
return (int) (this.time - o.time);
}
public long getTime(){
return this.time;
}
public void run(){
task.run();
}
}
//自己实现一个定时器
class MyTimer{
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
private Object locker = new Object();
public void schedule(Runnable task,long delay){
synchronized (locker) {
//以入队列这个时刻作为时间基准
MyTimerTask timerTask = new MyTimerTask(task, System.currentTimeMillis() + delay);
queue.offer(timerTask);
locker.notify();
}
}
public MyTimer(){
//创建一个线程负责执行队列中的任务
Thread t = new Thread(() ->{
try {
while (true) {
synchronized (locker) {
//取出队首元素
//对于wait,保险起见还是用while
while (queue.isEmpty()) {
//continue;
locker.wait();
}
MyTimerTask task = queue.peek();
if (System.currentTimeMillis() < task.getTime()) {
//当前任务时间,如果比系统时间大,说明任务执行时机未到
//continue;用continue就一直等着占资源
locker.wait(task.getTime() - System.currentTimeMillis());
} else {
//时间到了,执行任务
task.run();
queue.poll();
}
}
}
}catch (InterruptedException e){
throw new RuntimeException();
}
});
t.start();
}
}
public class 实现定时器 {
public static void main(String[] args) {
MyTimer timer = new MyTimer();
//和线程池一样,Timer也包含前台线程,阻止进程结束
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Hello,Timer2000");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Hello,Timer1000");
}
},1000);
System.out.println("Hello main");
}
}
1)MyTimerTask:把“任务”和“执行时刻”绑在一起
class MyTimerTask{
private Runnable task;
private long time; // 任务应执行的绝对时间点(毫秒时间戳)
}
Runnable task:真正要执行的逻辑(相当于“回调函数”)。long time:这个任务应该在什么时候执行(用System.currentTimeMillis()的时间戳表示)。
schedule(task, delay) 会把 “delay 毫秒后执行” 转成 “在 now + delay 这个绝对时刻执行”。
2)PriorityQueue:让“最近要执行的任务”永远在队首
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
PriorityQueue 是小根堆(最小值优先)。只要比较规则按 time 排序:
peek()取出的永远是最早要执行的任务poll()弹出的也是最早要执行的任务
这就实现了计时器最关键的功能:无论提交顺序如何,都按触发时间先后执行。
3)schedule:提交一个“未来任务”,顺便叫醒工作线程
public void schedule(Runnable task,long delay){
synchronized (locker) {
MyTimerTask timerTask = new MyTimerTask(task, System.currentTimeMillis() + delay);
queue.offer(timerTask);
locker.notify();
}
}
这里做了三件事:
- 加锁:保护
queue(多个线程同时schedule时必须线程安全)。 - 计算触发时间:
now + delay。 - 入队:放入优先队列。
- notify 唤醒:如果工作线程正在等待(队列空 / 或者在做超时等待),叫醒它重新计算“下一件事什么时候执行”。
4)计时器线程:一直盯着队首任务,没到点就 wait(剩余时间)
构造函数里创建了一个线程,线程逻辑是计时器的心脏:
while (true) {
synchronized (locker) {
while (queue.isEmpty()) locker.wait();
MyTimerTask task = queue.peek();
if (System.currentTimeMillis() < task.getTime()) {
locker.wait(task.getTime() - System.currentTimeMillis());
} else {
task.run();
queue.poll();
}
}
}
关键点拆解
A. 队列空:无限期等待
while (queue.isEmpty()) locker.wait();
- 没有任务就睡到有人
schedule并notify。 - 用
while不用if:防止虚假唤醒/竞争唤醒后条件又不成立。
B. 队列不空:看队首任务什么时候执行
MyTimerTask task = queue.peek();
队首就是“下一件最早要做的事”。
C. 时间没到:带超时等待
locker.wait(task.getTime() - now);
这一步特别关键:不是 busy loop 空转,而是让线程“睡到差不多该醒的时刻”,节省 CPU。
D. 时间到了:执行任务并出队
task.run();
queue.poll();
5)用一个例子理解执行顺序
假设提交两个任务:
- A:延迟 2000ms
- B:延迟 1000ms
优先队列会让 B 的 time 更小,所以 B 在队首:
- 工作线程
peek()得到 B,发现没到点 →wait(剩余时间) - 到点醒来执行 B,
poll()把 B 移除 - 再
peek()得到 A,继续等待到 A 的时间点执行
结果一定是:1000ms 的先执行,2000ms 的后执行,即使提交顺序反过来也一样。
6)这段实现里有哪些明显问题(不修就跑不起来/容易出坑)
问题 1:MyTimerTask 没有实现 Comparable,PriorityQueue 可能无法排序
现在只有 compareTo 方法,但类声明里缺少:
class MyTimerTask implements Comparable<MyTimerTask> { ... }
并且比较最好别强转 int,可能溢出,建议:
@Override
public int compareTo(MyTimerTask o) {
return Long.compare(this.time, o.time);
}
问题 2:工作线程创建了但没有 start(),计时器根本不会运行
构造函数里:
Thread t = new Thread(() -> { ... });
缺少:
t.start();
没有 start,线程永远不会执行那段 while(true) 的调度逻辑。
问题 3:在 synchronized(locker) 里直接 task.run(),会拖死 schedule
当前代码在持锁状态下运行任务:
synchronized(locker) {
task.run(); // 这里执行任务
}
如果任务执行很慢,其他线程 schedule() 想往队列加任务会一直阻塞在锁上,吞吐很差。
更合理的结构是:锁内只做队列操作,锁外执行任务(先 poll 出来再跑)。
问题 4:main 里用的是 Timer,不是 MyTimer
main 里演示的是 JDK 的 java.util.Timer,并没有实际调用自写的 MyTimer。要演示自写计时器,应类似:
MyTimer timer = new MyTimer();
timer.schedule(() -> System.out.println("Hello 2000"), 2000);
timer.schedule(() -> System.out.println("Hello 1000"), 1000);
System.out.println("Hello main");
7)给一份“能正确跑、并且更像标准实现”的最小修正版
import java.util.PriorityQueue;
class MyTimerTask implements Comparable<MyTimerTask> {
private final Runnable task;
private final long time;
public MyTimerTask(Runnable task, long time) {
this.task = task;
this.time = time;
}
@Override
public int compareTo(MyTimerTask o) {
return Long.compare(this.time, o.time);
}
public long getTime() { return time; }
public void run() { task.run(); }
}
class MyTimer {
private final PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
private final Object locker = new Object();
public MyTimer() {
Thread t = new Thread(() -> {
try {
while (true) {
MyTimerTask taskToRun;
synchronized (locker) {
while (queue.isEmpty()) {
locker.wait();
}
long now = System.currentTimeMillis();
MyTimerTask head = queue.peek();
long waitTime = head.getTime() - now;
if (waitTime > 0) {
locker.wait(waitTime);
continue;
}
taskToRun = queue.poll(); // 先取出来
}
// 锁外执行,避免阻塞 schedule
taskToRun.run();
}
} catch (InterruptedException e) {
// 允许线程退出(也可以选择恢复中断标记后退出)
Thread.currentThread().interrupt();
}
});
t.start();
}
public void schedule(Runnable task, long delay) {
synchronized (locker) {
queue.offer(new MyTimerTask(task, System.currentTimeMillis() + delay));
locker.notify(); // 单线程工作者,notify 足够;更通用可 notifyAll
}
}
}
总结
- 用 优先队列 保证“最早执行的任务”总在队首
- 用 单工作线程 循环:队列空就等,时间没到就
wait(剩余时间),到点就执行 - 用
synchronized + wait/notify做线程安全与条件同步
这就是一个计时器的骨架:简单、可推理、可扩展(后续还能加重复任务、取消任务、关闭计时器等能力)。
更多推荐

所有评论(0)