一、定时器
1、定时器是什么
定时器也是软件开发中的一个重要组件,类似于一个"闹钟",达到一个设定的时间之后,就执行某个指定好的代码
定时器是一种实际开发中非常常用的组件。
比如网络通信中,如果对方500ms内没有返回数据,则断开连接尝试重连。
比如一⼀个Map,希望里面的某个key在3s之后过期(自动删除)。
类似于这样的场景就需要用到定时器。

2、标准库中的定时器
标准库中提供了一个Timer类,Timer类的核心方法为schedule
schedule包含两个参数,第一个参数指定即将要执行的任务代码,第⼆个参数指定多长时间之后执行(单位为毫秒)
java
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
}, 3000);
3、实现定时器
定时器的构成
一个带优先级队列(不要使用PriorityBlockingQueue,容易死锁!)
队列中的每个元素是一个Task对象
Task中带有一个时间属性,队首元素就是即将要执行的任务
同时有一个worker线程一直扫描队首元素,看队首元素是否需要执行
1)Timer类提供的核心接口为schedule,用于注册一个任务,并指定这个任务多长时间后执行
java
public class MyTimer {
public void schedule(Runnable command, long after) {
// TODO
}
}
2)Task类用于描述⼀个任务(作为Timer的内部类),里面包含一个Runnable对象和一个time(毫秒时间戳)
这个对象需要放到优先队列中,因此需要实现 Comparable 接口
java
class MyTask implements Comparable<MyTask> {
public Runnable runnable;
// 为了⽅便后续判定, 使⽤绝对的时间戳.
public long time;
public MyTask(Runnable runnable, long delay) {
this.runnable = runnable;
// 取当前时刻的时间戳 + delay, 作为该任务实际执⾏的时间戳
this.time = System.currentTimeMillis() + delay;
}
@Override
public int compareTo(MyTask o) {
// 这样的写法意味着每次取出的是时间最⼩的元素.
// 到底是谁减谁?? 俺也记不住!!! 随便写⼀个, 执⾏下, 看看效果~~
return (int)(this.time - o.time);
}
}
3)Timer实例中,通过PriorityQueue来组织若干个Task对象
通过schedule来往队列中插入一个个Task对象
java
class MyTimer {
// 核⼼结构
private PriorityQueue<MyTask> queue = new PriorityQueue<>();
// 创建⼀个锁对象
private Object locker = new Object();
public void schedule(Runnable command, long after) {
// 根据参数, 构造 MyTask, 插⼊队列即可.
synchronized (locker) {
MyTask myTask = new MyTask(runnable, delay);
queue.offer(myTask);
locker.notify();
}
}
}
4)Timer类中存在一个worker线程,一直不停的扫描队首元素,看看是否能执行这个任务
所谓"能执行"指的是该任务设定的时间已经到达了
java
// 在这⾥构造线程, 负责执⾏具体任务了.
public MyTimer() {
Thread t = new Thread(() -> {
while (true) {
try {
synchronized (locker) {
// 阻塞队列, 只有阻塞的⼊队列和阻塞的出队列, 没有阻塞的查看队⾸元素.
while (queue.isEmpty()) {
locker.wait();
}
MyTask myTask = queue.peek();
long curTime = System.currentTimeMillis();
if (curTime >= myTask.time) {
// 时间到了, 可以执⾏任务了
queue.poll();
myTask.runnable.run();
} else {
// 时间还没到
locker.wait(myTask.time - curTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
4. 为什么主线程不会被 "阻塞"?
因为 Timer 的调度是异步 的(由独立线程执行),主线程在调用 timer.schedule 后会 "继续往下执行",不会停在那里等 2 秒。
可以类比成:你(主线程)点了一份外卖(定时任务),然后继续做自己的事(打印 hello main);外卖小哥(Timer 线程)会在 2 秒后把外卖送到(执行 hello timer)。
Java 中 Timer 线程特性:
- 线程类型 :Timer 内部包含前台线程(非守护线程)。
- 进程影响:前台线程会阻止 Java 进程结束,即使主线程执行完毕,只要 Timer 的前台线程还在运行,进程就会保持存活。
- 类比线程池 :和线程池(若未显式配置守护线程)类似,都会因前台线程的存在,让进程无法自动终止,需主动关闭 Timer(如调用
timer.cancel())来释放资源。
模拟实现定时器
1. 任务类设计
创建一个类表示定时任务,需包含任务逻辑 和执行时间(用于判断何时执行)。
2. 任务管理容器选择
- 若用
ArrayList管理多个任务,每次需遍历找到 "执行时间最早" 的任务,效率较低。 - 更优方案是使用优先级队列(
PriorityQueue),基于 "执行时间" 排序,让时间最早的任务始终在队首,可高效获取待执行任务。
3. schedule 方法实现
该方法负责将任务添加到优先级队列中,完成任务的注册。
4. 执行线程设计
额外创建一个独立线程,循环执行以下逻辑:
- 检查队首任务的执行时间是否到达;
- 若时间未到,线程等待(直到时间到达或被唤醒);
- 若时间到达,执行队首任务并将其从队列中移除。
定时器的线程安全问题,核心逻辑如下:
- 存在两个线程操作同一个队列:一个线程调用
schedule方法向队列中添加任务,另一个是定时器内部的线程从队列中获取并执行任务。 - 多线程同时操作同一个队列时,若没有合适的同步机制,会出现线程安全问题(如任务添加或获取时的竞态条件、数据不一致等)。
- 解决方法是对队列的操作(添加、获取任务)进行同步控制,例如使用
synchronized关键字、ReentrantLock等锁机制来保证线程安全。
计算任务的目标执行时间:任务的目标执行时间 = 任务提交时的系统时间 + 设定的延时时间(毫秒)
在 Lambda 表达式中,若涉及锁(如 synchronized 或 Lock),this 的指向始终是外部类的实例 ,而非 Lambda 表达式本身(因为 Lambda 表达式不是匿名内部类 ,它没有自己的 this)。
关键原因:Lambda 与匿名内部类的本质区别
- 匿名内部类 :是一个独立的类实例,有自己的
this(指向匿名内部类实例),因此在匿名内部类中使用this时,指向的是该内部类自身。 - Lambda 表达式 :是 "函数式接口的实例化简化",它没有独立的
this,其内部的this与所在外部类的this完全一致(即指向外部类的当前实例)。
注意:在锁里的this 指向的是外面的类的
Lambda 表达式没有自己的 this。
在 Lambda 表达式内部使用 this 时,this 指向的是创建 Lambda 表达式的外部类的当前实例(即包围 Lambda 表达式的那个类的实例)。
原因:
Lambda 表达式本质是 "函数式接口的实例化简化",但它不是一个独立的类 (不同于匿名内部类),也不会生成独立的类对象。它更像是一个 "嵌入在外部类中的代码块",因此不具备自身的 this 引用。
- Lambda 表达式内部的
this等价于外部类的this,指向外部类实例。 - Lambda 表达式自身没有独立的
this引用(因它不是独立的类实例)。
实现单线程的定时器,如果一口气来10000个任务一个一个的任务执行也太慢了,可以结合定时器来使用

定时任务的多线程执行架构设计思路,核心是通过 "分工协作" 的多线程模型来高效处理大量定时任务:
1. 线程角色分工
- 扫描线程:单独线程负责扫描定时任务队列,判断任务的执行时间是否到达。
- 执行线程池:由多个线程组成的线程池,负责实际执行到达时间的任务。
2. 流程逻辑
扫描线程持续监控任务队列,当检测到任务执行时间到达时,将该任务从定时队列中取出,提交到执行线程池的任务队列中;执行线程池中的多个线程并行从队列中获取任务并执行,从而实现任务的高效并发处理。
3. 场景适配性
针对 "一口气注册 100000 个 14:00 执行的任务" 这类大规模定时任务场景,该架构能有效解决单线程执行的性能瓶颈:
- 扫描线程只需专注于时间判断和任务分发,避免因任务执行耗时阻塞扫描逻辑;
- 执行线程池的多线程并行执行,可充分利用 CPU 资源,确保大量任务在时间点到达时能快速完成执行。
java
package thread;
// 这样的写法基于抽象类的方式定义 MyTimerTask
// 这样的定义虽然确实可以, 写起来有点麻烦.
// 还有另外的写法.
//abstract class MyTimerTask implements Runnable {
// @Override
// public abstract void run();
//}
import java.util.PriorityQueue;
import java.util.concurrent.Executors;
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable task;
// 记录任务要执行的时刻
private long time;
public MyTimerTask(Runnable task, long time) {
this.task = task;
this.time = time;
}
@Override
public int compareTo(MyTimerTask o) {
return (int) (this.time - o.time);
// return (int) (o.time - this.time);
}
public long getTime() {
return time;
}
public void run() {
task.run();
}
}
// 自己实现一个定时器
class MyTimer {
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
// 直接使用 this 作为锁对象, 当然也是 ok 的
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) {
// 取出队首元素
// 还是加上 while
while (queue.isEmpty()) {
// 这里的 sleep 时间不好设定!!
locker.wait();
}
MyTimerTask task = queue.peek();
if (System.currentTimeMillis() < task.getTime()) {
// 当前任务时间, 如果比系统时间大, 说明任务执行的时机未到
locker.wait(task.getTime() - System.currentTimeMillis());
} else {
// 时间到了, 执行任务
task.run();
queue.poll();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
}
public class demo38 {
public static void main(String[] args) {
MyTimer timer = new MyTimer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 3000");
}
}, 3000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 2000");
}
}, 2000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 1000");
}
}, 1000);
Executors.newScheduledThreadPool(4);
}
}
用 PriorityQueue 实现简易定时器(MyTimer)的例子 ,相当于自己实现了一个简化版的 java.util.Timer。
一、用 PriorityQueue 实现简易定时器(MyTimer)的例子 ,相当于自己实现了一个简化版的 java.util.Timer。
java
Timer timer = new Timer();
timer.schedule(task, delay);
但不用 Java 内置的 Timer 或 ScheduledExecutorService,而是自己实现时间调度机制。
1. MyTimerTask ------ 封装任务与执行时间
java
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable task; // 要执行的任务
private long time; // 任务应该执行的绝对时间(单位:毫秒)
public MyTimerTask(Runnable task, long time) {
this.task = task;
this.time = time;
}
public long getTime() { return time; }
public void run() { task.run(); }
@Override
public int compareTo(MyTimerTask o) {
return (int)(this.time - o.time); // 时间早的任务排在前面
}
}
作用:
-
把任务(
Runnable)和执行时间绑定起来; -
实现
Comparable接口,让PriorityQueue能自动按照时间排序。
2.MyTimer ------ 定时器调度核心
java
class MyTimer {
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
private Object locker = new Object();
public MyTimer() {
Thread t = new Thread(() -> {
try {
while (true) {
synchronized (locker) {
while (queue.isEmpty()) {
locker.wait();
}
MyTimerTask task = queue.peek();
long current = System.currentTimeMillis();
if (current < task.getTime()) {
// 时间还没到,等待差值时间
locker.wait(task.getTime() - current);
} else {
// 时间到了,执行并出队
queue.poll().run();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
public void schedule(Runnable task, long delay) {
synchronized (locker) {
MyTimerTask timerTask = new MyTimerTask(task, System.currentTimeMillis() + delay);
queue.offer(timerTask);
locker.notify(); // 唤醒线程重新判断是否有更早的任务
}
}
}
逻辑解释:
-
一个后台线程 持续工作(
while (true))。 -
所有任务放进优先队列,时间早的排前面。
-
如果队列空 → 线程
wait()等待。 -
如果时间没到 → 按剩余时间继续
wait(remainingTime)。 -
如果时间到了 → 执行
run()并出队。
💡 使用 PriorityQueue 的好处:
-
自动保证最早的任务总在队首;
-
无需每次遍历整个队列找最早任务。
3.主程序测试
java
public class demo38 {
public static void main(String[] args) {
MyTimer timer = new MyTimer();
timer.schedule(() -> System.out.println("hello 3000"), 3000);
timer.schedule(() -> System.out.println("hello 2000"), 2000);
timer.schedule(() -> System.out.println("hello 1000"), 1000);
}
}


开始实现模拟定时器
1. 总体目标与约束
目标:实现一个简易定时器 ,支持 schedule(Runnable task, long delay),按时间顺序执行任务。
核心约束/设计选择(隐含假设):
-
使用一条调度线程来管理何时执行哪个任务(单调调度线程)。
-
使用
PriorityQueue存放待执行任务,按执行时间排序。 -
线程间用一个对象锁与
wait()/notify()协调(最基础的同步手段)。 -
任务执行通过调用
Runnable.run()(默认在调度线程里直接执行)。
2. MyTimerTask 的设计(封装任务与执行时间)
java
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable task;
private long time; // 绝对执行时间(毫秒)
public MyTimerTask(Runnable task, long time) { ... }
public long getTime() { return time; }
public void run() { task.run(); }
@Override public int compareTo(MyTimerTask o) { return (int)(this.time - o.time); }
}
为什么要这样封装?
-
把任务和时间绑定 :
Runnable本身没有时间属性,包装成对象能把"任务"和"什么时候执行"绑定在一起,便于排序和管理。 -
实现 Comparable :
PriorityQueue通过元素比较来决定队首元素。实现compareTo让队列始终能把最早执行的任务放在队首,便于只关注队首任务即可决定下一步动作。 -
提供
run()方法 :对外统一调用run(),便于未来替换实现或在调度线程外执行(若改成线程池执行只需改run()的调用位置)。
注意/改进点
-
compareTo内直接做(int)(this.time - o.time)会溢出,应使用Long.compare(this.time, o.time)。原因:两个 long 相减再转 int 可能丢失或溢出,导致错排。
-
time存绝对时间(System.currentTimeMillis()+ delay)还是相对延迟(delay)有差别:-
绝对时间便于比较、处理调度时差;
-
但对"时间回拨/系统时钟变化"敏感(见后文)。
-
-
可以扩展字段(id、cancelled flag、period 等)以支持取消、重复任务等功能。
3. 使用 PriorityQueue 存任务 ------ 为什么是好选择
PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
设计动机
-
快速定位最早要执行的任务 :调度逻辑只需看队首
peek()即可知道下一次要执行的任务,而无需遍历所有任务。 -
插入操作复杂度较低:插入是 O(log n),适合不极端的大量任务场景。
-
语义清晰:优先队列天然表达"按时间优先"的语义。
风险与考虑
-
PriorityQueue不是线程安全 的 ------ 必须外部加锁(本例用synchronized(locker))。 -
PriorityQueue对重复时间、相等时间等行为是可接受的,但若需要稳定性(相同时间的先后顺序),可能需要额外字段(序号)保证一致性。 -
如果任务量很大或需要高性能,可考虑更专业的数据结构(时间轮、HashedWheelTimer、heap 的并发版本等)。
4. 锁对象与同步:locker 和 synchronized
java
private Object locker = new Object();
synchronized (locker) { ... }
locker.wait(...);
locker.notify();
为什么要同步?
-
PriorityQueue不是线程安全的;schedule()由任意线程调用(外部线程),调度线程在后台访问队列。必须用同步保护队列的并发访问,避免数据结构破坏(race condition、heap 结构损坏)。 -
wait()/notify()必须在同步块内调用(Java 语言要求),因此选择用locker作为监视器对象。
为什么不直接用 synchronized(this)?
可以用 this,但用独立的 locker 更明确、可替换(例如以后把锁替换为 ReentrantLock 时更方便),也可避免暴露锁给外部代码(若 this 被外部 synchronized 使用,容易增加耦合)。
5. 唤醒机制:为什么 schedule() 要 notify()?
timer.schedule(...) 插入任务后调用 locker.notify();
为什么需要唤醒?
-
调度线程可能正在
wait(remaining)------ 等待当前队首任务的到期时间。如果新插入的任务比当前队首任务更早(也就是需要更早执行),必须唤醒调度线程,让它重新计算应该等待多久(或者立刻执行新任务)。否则将错过更早任务的执行时刻。 -
notify()的作用是唤醒等待线程,调度线程就会从wait()返回并重新检查队列和时间差。
notify() 还是 notifyAll()?
-
在当前设计(只有一个调度线程)
notify()足够且更高效。 -
若未来有多个等待线程(比如为了扩展而启动多个调度线程),需要
notifyAll()或更复杂的协调机制。 -
安全实践:当存在多种条件 或多个等待者时,
notifyAll()更稳妥(避免死锁或遗漏唤醒),但代价是多线程被无谓唤醒。
6. 调度线程的核心循环
java
while (true) {
synchronized (locker) {
while (queue.isEmpty()) {
locker.wait();
}
MyTimerTask task = queue.peek();
long current = System.currentTimeMillis();
if (current < task.getTime()) {
locker.wait(task.getTime() - current);
} else {
queue.poll();
task.run();
}
}
}
逐行解释并说明为什么这样写:
-
while (true):线程一直运行,负责不断调度任务。设计选择:常驻调度线程,直到被中断/关闭。- 替代:可增加退出条件(如
shutdown标志)来优雅终止线程。
- 替代:可增加退出条件(如
-
synchronized (locker):在整个检查-等待-取出的过程中必须持锁,保证检查队列状态和wait/poll的原子性,防止竞态。 -
while (queue.isEmpty()) locker.wait();-
使用
while而非if:防止虚假唤醒 (Java 的wait()可被 spuriously wake up),以及在被唤醒后需要再次检查队列是否真有任务。 -
如果用
if,虚假唤醒会导致线程继续执行,peek()可能返回 null,抛 NPE。
-
-
MyTimerTask task = queue.peek();--- 只读取队首但不出队。- 为什么不直接
poll()?因为如果时间未到不能丢弃或提前执行;peek()允许检查时间再决定是否等待或执行。
- 为什么不直接
-
if (current < task.getTime()) locker.wait(task.getTime() - current);-
用
wait(timeout)让线程在剩余时间内等待,减少 CPU 消耗(比 busy-wait 好)。当到期或被notify()(或被中断/虚假唤醒)返回后,循环会重新检查条件。 -
使用
long差值要小心负数(若系统时间变动或计算误差),要确保传给wait的值非负(可Math.max(0, ...))。
-
-
else { queue.poll(); task.run(); }-
时间到后出队并执行。顺序:先
poll()再run()可避免任务在执行中再次被.peek到(线程安全)。 -
但注意:如果
task.run()抛异常会中断调度线程的循环(若异常没有捕获)。因此在实际中应把task.run()包在try-catch内,防止一个任务崩掉整个调度线程。
-
为什么使用 wait(timeout) 而不是 sleep(timeout)?
sleep只会睡眠当前线程,不与锁/条件交互:如果使用sleep,在等待期间无法被notify()提前唤醒(只能被interrupt()打断)。那样当插入更早的任务时就无法及时调整等待时长,会导致延迟执行或复杂的轮询机制。wait(timeout)则在锁释放的同时进入等待状态,可接受notify()提前唤醒并重新判断。
7. 关于时间来源:System.currentTimeMillis() vs System.nanoTime()
代码中用 System.currentTimeMillis() + delay 计算绝对执行时刻。
问:为什么用 currentTimeMillis()?有风险吗?
-
currentTimeMillis()返回墙钟时间(wall-clock),会受系统时间调整(NTP 校正、人工修改、夏令时等)影响。若系统时钟向后调整,可能产生长时间等待或错过任务;向前调整则可能导致任务提前执行。 -
更稳健的做法:对于代表时间间隔 的比较(等待多少毫秒),建议使用
System.nanoTime()(单调递增,用于测量间隔,不受系统时钟调整影响)。计算方式:记录deadlineNano = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(delay),比较nanoTime()与deadlineNano。
结论/建议:
-
如果你只关心"过了多少时间后执行"(延迟),用
nanoTime更安全。 -
如果你想在特定的"真实时刻"执行(例如"某日某点"),需要
currentTimeMillis()。
8. 为什么用单调调度线程(调度线程也执行任务)?利与弊
优点:
-
实现简单:调度线程直接
task.run(),无需线程池或任务转发。 -
资源少:不创建额外线程(除调度线程外)。
缺点:
-
如果某个任务执行时间很长,会阻塞后续任务(调度延迟)。
-
不利于并发执行多个同一时刻的任务。
-
无法利用多核并行执行任务。
改进:
-
把任务提交给
ExecutorService(线程池)执行:调度线程只负责时间判断并executor.submit(task)。这样调度线程不会被单个任务阻塞;但需要处理任务提交失败或线程池饱和的情况。 -
例如:
java
MyTimerTask t = queue.poll();
executor.execute(() -> {
try { t.run(); } catch (Throwable e) { ... }
});
构造方法启动线程是为了:
- 初始化时就准备好"定时器引擎"(只启动一次)
- 避免每次 schedule 都重复创建线程
- 实现"单例后台线程"模型,所有任务共用一个执行线程
- 符合 Timer 的语义:创建即运行,随时可以调度任务
定时器用线程池实现
java
package thread;
import java.util.PriorityQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable task;
private long time;
private volatile boolean cancelled = false;
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() {
if (!cancelled) {
task.run();
}
}
public void cancel() {
cancelled = true;
}
public boolean isCancelled() {
return cancelled;
}
}
// 模拟实现的定时器 - 使用线程池但保持原有逻辑
class MyTimer {
private final PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
private final Object locker = new Object();
private final ExecutorService workerPool;
private final Thread scannerThread;
private volatile boolean running = true;
// 自定义线程工厂,给线程命名
private static class TimerThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
TimerThreadFactory(String poolName) {
namePrefix = poolName + "-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
t.setDaemon(true);
return t;
}
}
public MyTimer() {
this(1); // 默认使用1个工作线程
}
public MyTimer(int workerThreads) {
// 创建工作线程池
workerPool = Executors.newFixedThreadPool(workerThreads, new TimerThreadFactory("timer-worker"));
// 创建扫描线程(保持原有设计)
scannerThread = new Thread(() -> {
try {
scanAndExecute();
} catch (InterruptedException e) {
System.out.println("扫描线程被中断");
}
}, "timer-scanner");
scannerThread.setDaemon(true);
scannerThread.start();
}
public void schedule(Runnable task, long delay) {
if (delay < 0) {
throw new IllegalArgumentException("延迟时间不能为负数");
}
MyTimerTask timerTask = new MyTimerTask(task, System.currentTimeMillis() + delay);
synchronized (locker) {
queue.offer(timerTask);
locker.notify(); // 唤醒扫描线程
System.out.println(Thread.currentThread().getName() + " - 添加任务,延迟: " + delay + "ms, 队列大小: " + queue.size());
}
}
// 取消所有任务
public void cancel() {
synchronized (locker) {
for (MyTimerTask task : queue) {
task.cancel();
}
queue.clear();
locker.notify();
}
}
// 停止定时器
public void stop() {
running = false;
workerPool.shutdownNow();
scannerThread.interrupt();
synchronized (locker) {
locker.notifyAll();
}
}
// 获取待执行任务数量
public int getTaskCount() {
synchronized (locker) {
return queue.size();
}
}
// 核心扫描和执行逻辑
private void scanAndExecute() throws InterruptedException {
while (running && !Thread.currentThread().isInterrupted()) {
synchronized (locker) {
// 等待队列不为空
while (queue.isEmpty() && running) {
locker.wait();
}
if (!running) {
break;
}
// 检查队首任务
MyTimerTask task = queue.peek();
if (task == null) {
continue;
}
long currentTime = System.currentTimeMillis();
long taskTime = task.getTime();
long waitTime = taskTime - currentTime;
if (waitTime <= 0) {
// 任务时间已到,从队列移除
queue.poll();
if (!task.isCancelled()) {
// 使用线程池执行任务,而不是直接在当前线程执行
System.out.println(Thread.currentThread().getName() + " - 提交任务到线程池执行");
workerPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " - 开始执行任务");
task.run();
System.out.println(Thread.currentThread().getName() + " - 任务执行完成");
});
}
} else {
// 等待到任务执行时间
System.out.println(Thread.currentThread().getName() + " - 等待 " + waitTime + "ms");
locker.wait(waitTime);
}
}
}
System.out.println("扫描线程退出");
}
}
// 测试类
public class demo38 {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== 模拟实现定时器(线程池版)===");
// 创建定时器,使用2个工作线程
MyTimer timer = new MyTimer(2);
// 添加多个任务
for (int i = 1; i <= 5; i++) {
final int taskId = i;
timer.schedule(() -> {
System.out.println(Thread.currentThread().getName() + " - 任务" + taskId + " 执行");
try {
Thread.sleep(1000); // 模拟任务执行时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, i * 1000L); // 1秒, 2秒, 3秒...
}
// 添加一个长时间任务
timer.schedule(() -> {
System.out.println(Thread.currentThread().getName() + " - 长时间任务开始");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + " - 长时间任务结束");
}, 6000);
// 监控队列大小
Thread monitorThread = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
System.out.println("监控 - 待执行任务数: " + timer.getTaskCount());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "monitor-thread");
monitorThread.start();
// 等待所有任务执行
Thread.sleep(15000);
System.out.println("\n=== 测试任务取消 ===");
// 测试取消功能
MyTimer timer2 = new MyTimer(1);
// 添加一些任务
for (int i = 1; i <= 3; i++) {
final int taskId = i;
timer2.schedule(() -> {
System.out.println("定时器2 - 任务" + taskId + " 执行");
}, i * 2000L);
}
Thread.sleep(2500);
// 取消剩余任务
System.out.println("取消前任务数: " + timer2.getTaskCount());
timer2.cancel();
System.out.println("取消后任务数: " + timer2.getTaskCount());
Thread.sleep(2000);
// 停止定时器
timer.stop();
timer2.stop();
System.out.println("测试完成");
}
}
时间轮定时器
1. 数据结构设计
- 循环数组(时间轮主体):将时间划分为多个 "时间单位",用循环数组存储这些时间单位,数组的每个元素对应一个时间单位。
- 链表(任务容器):数组的每个元素关联一个链表,用于存储该时间单位内需要执行的定时任务。
2. 执行逻辑
- 定时器维护一个 "光标",每经过一个时间单位,光标就移动到数组的下一个元素(循环移动)。
- 当光标移动到某个数组元素时,就遍历该元素对应的链表,执行所有到期的任务。
3. 与基于堆的定时器对比
- 基于堆的定时器:通过优先级队列(堆)按任务执行时间排序,每次取出最早到期的任务执行,适用于任务数量不多的场景。
- 时间轮定时器:通过数组 + 链表的结构,将任务按时间单位分组,可高效处理大量定时任务(尤其是时间粒度相对固定的场景),时间复杂度更优。
时间轮适合时间精度高的定时器,优先级队列实现的定时器执行任务多的
一、时间轮定时器实现(高效处理大量任务)
时间轮通过 "循环数组 + 链表" 按时间单位分组管理任务,适合时间粒度固定(如 10ms/100ms)、任务数量庞大的场景。
java
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
/**
* 时间轮定时器
* 特点:按固定时间粒度(tickMs)分组任务,高效处理大量定时任务,时间精度由tickMs决定
*/
public class TimeWheelTimer {
// 时间轮的时间粒度(每个格子代表的毫秒数,决定精度)
private final long tickMs;
// 时间轮的格子数量(循环数组大小)
private final int wheelSize;
// 时间轮总覆盖时间范围(tickMs * wheelSize)
private final long totalRangeMs;
// 循环数组:每个格子对应一个时间单位,存储该单位内的任务链表
private final List<TimerTask>[] buckets;
// 当前指针位置(指向当前时间单位对应的格子)
private int currentIndex;
// 当前时间轮的绝对时间(毫秒)
private long currentTime;
// 工作线程:推动时间轮前进并执行任务
private final Thread workerThread;
// 锁:保证线程安全
private final ReentrantLock lock = new ReentrantLock();
// 标记是否运行
private volatile boolean isRunning;
// 定时任务封装
static class TimerTask {
Runnable task; // 实际任务
long delayMs; // 延迟时间(用户传入)
long targetTime; // 目标执行时间(绝对时间)
public TimerTask(Runnable task, long delayMs, long targetTime) {
this.task = task;
this.delayMs = delayMs;
this.targetTime = targetTime;
}
}
/**
* 初始化时间轮
* @param tickMs 时间粒度(毫秒,如100ms,精度越高性能消耗略大)
* @param wheelSize 格子数量(总范围 = tickMs * wheelSize)
*/
public TimeWheelTimer(long tickMs, int wheelSize) {
this.tickMs = tickMs;
this.wheelSize = wheelSize;
this.totalRangeMs = tickMs * wheelSize;
// 初始化循环数组(每个格子是一个任务链表)
this.buckets = new List[wheelSize];
for (int i = 0; i < wheelSize; i++) {
buckets[i] = new LinkedList<>();
}
this.currentTime = System.currentTimeMillis();
this.currentIndex = 0;
this.isRunning = true;
// 启动工作线程
this.workerThread = new Thread(this::run, "TimeWheel-Worker");
this.workerThread.start();
}
// 添加定时任务
public void schedule(Runnable task, long delayMs) {
if (delayMs < 0) {
throw new IllegalArgumentException("延迟时间不能为负数");
}
long targetTime = System.currentTimeMillis() + delayMs;
TimerTask timerTask = new TimerTask(task, delayMs, targetTime);
lock.lock();
try {
// 计算任务应放入的格子索引
int bucketIndex = calculateBucketIndex(targetTime);
buckets[bucketIndex].add(timerTask);
} finally {
lock.unlock();
}
}
// 计算任务所在的格子索引
private int calculateBucketIndex(long targetTime) {
// 计算与当前时间的差值
long offset = targetTime - currentTime;
// 若超过总范围,取模(简化处理,多层时间轮可优化)
if (offset >= totalRangeMs) {
offset %= totalRangeMs;
}
// 计算索引(当前指针 + 偏移的格子数)% 总格子数
return (currentIndex + (int) (offset / tickMs)) % wheelSize;
}
// 工作线程逻辑:推动时间轮
private void run() {
while (isRunning) {
try {
// 休眠一个时间粒度,推动时间轮前进
Thread.sleep(tickMs);
lock.lock();
try {
currentTime += tickMs; // 更新当前时间
currentIndex = (currentIndex + 1) % wheelSize; // 移动指针
// 取出当前格子的所有任务
List<TimerTask> currentBucket = buckets[currentIndex];
if (!currentBucket.isEmpty()) {
// 执行所有任务(用新线程执行,避免阻塞时间轮)
for (TimerTask task : currentBucket) {
new Thread(task.task, "Task-Executor").start();
}
currentBucket.clear(); // 清空当前格子
}
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
if (!isRunning) {
break;
}
}
}
}
// 停止定时器
public void stop() {
isRunning = false;
workerThread.interrupt();
}
// 测试
public static void main(String[] args) throws InterruptedException {
// 创建时间轮:100ms精度,100个格子(总覆盖10秒)
TimeWheelTimer timer = new TimeWheelTimer(100, 100);
// 添加1000个任务(模拟大量任务)
for (int i = 0; i < 1000; i++) {
final int num = i;
// 任务延迟1-3秒随机分布
long delay = 1000 + new Random().nextInt(2000);
timer.schedule(() -> System.out.println("任务" + num + "执行,延迟" + delay + "ms"), delay);
}
// 运行5秒后停止
Thread.sleep(5000);
timer.stop();
}
}
二、基于堆(优先级队列)的定时器实现(适合任务量不大的场景)
基于优先级队列(小顶堆)按任务执行时间排序,每次取出最早到期的任务执行,实现简单但大量任务时性能略低。
java
import java.util.PriorityQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 基于堆(优先级队列)的定时器
* 特点:实现简单,适合任务数量不多的场景,时间精度高(依赖系统时间)
*/
public class HeapBasedTimer {
// 优先级队列(小顶堆):按任务执行时间升序排序
private final PriorityQueue<TimerTask> taskQueue;
// 锁和条件变量:保证线程安全和等待唤醒
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
// 工作线程:执行到期任务
private final Thread workerThread;
// 标记是否运行
private volatile boolean isRunning;
// 定时任务封装(实现Comparable按执行时间排序)
static class TimerTask implements Comparable<TimerTask> {
Runnable task; // 实际任务
long targetTime; // 目标执行时间(绝对时间)
public TimerTask(Runnable task, long targetTime) {
this.task = task;
this.targetTime = targetTime;
}
@Override
public int compareTo(TimerTask o) {
// 按目标时间升序排序(小顶堆)
return Long.compare(this.targetTime, o.targetTime);
}
}
public HeapBasedTimer() {
this.taskQueue = new PriorityQueue<>();
this.isRunning = true;
// 启动工作线程
this.workerThread = new Thread(this::run, "HeapTimer-Worker");
this.workerThread.start();
}
// 添加定时任务
public void schedule(Runnable task, long delayMs) {
if (delayMs < 0) {
throw new IllegalArgumentException("延迟时间不能为负数");
}
long targetTime = System.currentTimeMillis() + delayMs;
TimerTask timerTask = new TimerTask(task, targetTime);
lock.lock();
try {
taskQueue.add(timerTask);
condition.signal(); // 唤醒等待的工作线程
} finally {
lock.unlock();
}
}
// 工作线程逻辑:循环执行最早到期的任务
private void run() {
while (isRunning) {
lock.lock();
try {
// 循环检查队列是否为空
while (taskQueue.isEmpty()) {
condition.await(); // 空队列时等待
}
// 取出最早到期的任务
TimerTask task = taskQueue.peek();
long currentTime = System.currentTimeMillis();
if (currentTime < task.targetTime) {
// 未到期,等待剩余时间
condition.await(task.targetTime - currentTime);
} else {
// 到期,执行任务并移除
taskQueue.poll();
new Thread(task.task, "Task-Executor").start();
}
} catch (InterruptedException e) {
if (!isRunning) {
break;
}
} finally {
lock.unlock();
}
}
}
// 停止定时器
public void stop() {
isRunning = false;
workerThread.interrupt();
}
// 测试
public static void main(String[] args) throws InterruptedException {
HeapBasedTimer timer = new HeapBasedTimer();
// 添加少量任务(适合该定时器的场景)
timer.schedule(() -> System.out.println("任务1执行(延迟1000ms)"), 1000);
timer.schedule(() -> System.out.println("任务2执行(延迟2000ms)"), 2000);
timer.schedule(() -> System.out.println("任务3执行(延迟500ms)"), 500);
// 运行3秒后停止
Thread.sleep(3000);
timer.stop();
}
}

总结
- 时间轮定时器:适合高并发场景,尤其是任务数量庞大且时间粒度相对固定的情况(如分布式系统中的定时任务),通过分组批量处理任务提升效率。
- 基于堆的定时器:实现简单,适合任务量不大、需要灵活处理任意延迟时间的场景(如单机小量定时任务),但大量任务时性能会下降。
