定时器是什么
定时器也是软件开发中的⼀个重要组件. 类似于⼀个 "闹钟". 达到⼀个设定的时间之后, 就执⾏某个指定好的代码. 前端/后端中都会用到计时器.
定时器是⼀种实际开发中⾮常常⽤的组件. ⽐如⽹络通信中, 如果对⽅ 500ms 内没有返回数据, 则断开连接尝试重连. ⽐如⼀个 Map, 希望⾥⾯的某个 key 在 3s 之后过期(⾃动删除). 类似于这样的场景就需要⽤到定时器.
标准库中的定时器
• 标准库中提供了⼀个 Timer 类. Timer 类的核⼼⽅法为 schedule .
• schedule 包含两个参数. 第⼀个参数指定即将要执⾏的任务代码, 第⼆个参数指定多⻓时间之后 执⾏ (单位为毫秒).
// 定时器的使用
public class Demo21 {
public static void main(String[] args) {
Timer timer = new Timer();
// main 方法中调用 timer.schedule 方法时,
// 它只是将任务注册到 Timer 中,并告诉 Timer
// 在 3000 毫秒后执行这个任务。
// 任务的执行是由 Timer 内部的守护线程完成的。
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 3");
}
}, 3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 2");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 1");
}
}, 1000);
System.out.println("程序开始执行!");
}
}
模拟实现定时器
那么该怎么解决呢?
class MyTimerTask {
// 任务啥时候执行. 毫秒级的时间戳.
private long time;
// 任务具体是啥.
private Runnable runnable;
public long getTime() {
return time;
}
public Runnable getRunnable() {
return runnable;
}
public MyTimerTask(Runnable runnable, long delay) {
// delay 是一个相对的时间差. 形如 3000 这样的数值.
// 构造 time 要根据当前系统时间和 delay 进行构造.
time = System.currentTimeMillis() + delay;
this.runnable = runnable;
}
}
// 定时器的本体
class MyTimer {
// 使用优先级队列 来保存上述的N个任务
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
// 定时器的核心方法 就是把要执行的任务添加到队列中
public void schedule(Runnable runnable, long delay) {
MyTimerTask task = new MyTimerTask(runnable, delay);
queue.offer(task);
}
// MyTimer 中还需要构造一个 "扫描线程", 一方面去负责监控队首元素是否到点了,
// 是否应该执行;
// 一方面当任务到点之后,就要调用这里的 Runnable 的 Run 方法来完成任务
public MyTimer() {
// 扫描线程
Thread t1 = new Thread(() -> {
// 不停地去扫描当前的队首元素
while (true) {
try {
if (queue.isEmpty()) {
continue;
}
MyTimerTask task = queue.peek();
long curTime = System.currentTimeMillis();
if (curTime > task.getTime()) {
// 假设当前时间是 14:01, 任务时间是 14:00,
// 此时就意味着应该要执行这个任务了.
// 需要执行任务.
queue.poll();
task.getRunnable().run();
}else {
// 让当前线程休眠一下, 按照时间差来休眠.
Thread.sleep(task.getTime() - curTime);
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
}
}
上述代码写完了计时器的核心逻辑, 但是这份代码中还有几个关键性的问题.
最后完整的模拟实现代码.
import java.util.PriorityQueue;
import java.util.Timer;
/**
* Created with IntelliJ IDEA.
* Description:
* User: xiaotutu
* Date: 2025-02-20
* Time: 21:41
*/
class MyTimerTask implements Comparable<MyTimerTask>{
// 任务啥时候执行. 毫秒级的时间戳.
private long time;
// 任务具体是啥.
private Runnable runnable;
public long getTime() {
return time;
}
public Runnable getRunnable() {
return runnable;
}
public MyTimerTask(Runnable runnable, long delay) {
// delay 是一个相对的时间差. 形如 3000 这样的数值.
// 构造 time 要根据当前系统时间和 delay 进行构造.
time = System.currentTimeMillis() + delay;
this.runnable = runnable;
}
@Override
public int compareTo(MyTimerTask o) {
// 认为时间小的, 优先级高. 最终时间最小的元素, 就会放到队首.
// 怎么记忆, 这里是谁减去谁?? 不要记!! 记容易记错~~
// 随便写一个顺序, 然后实验一下就行了.
return (int) (this.time - o.time);
// return (int) (o.time - this.time);
}
}
// 定时器的本体
class MyTimer {
// 使用优先级队列 来保存上述的N个任务
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
// 用来加锁的对象
private Object locker = new Object();
// 定时器的核心方法 就是把要执行的任务添加到队列中
public void schedule(Runnable runnable, long delay) {
synchronized (locker) {
MyTimerTask task = new MyTimerTask(runnable, delay);
queue.offer(task);
// 每次来新的任务, 都唤醒一下之前的扫描线程.
// 好让扫描线程根据最新的任务情况, 重新规划等待时间.
locker.notify();
}
}
// MyTimer 中还需要构造一个 "扫描线程", 一方面去负责监控队首元素是否到点了,
// 是否应该执行;
// 一方面当任务到点之后,就要调用这里的 Runnable 的 Run 方法来完成任务
public MyTimer() {
// 扫描线程
Thread t1 = new Thread(() -> {
// 不停地去扫描当前的队首元素
while (true) {
try {
synchronized (locker) {
while (queue.isEmpty()) {
// 注意, 当前如果队列为空, 此时就不应该去取这里的
// 元素. 此处使用 wait 等待更合适.
// 如果使用 continue, 就会使这个线程
// while 循环运行的飞快,
// 也会陷入一个高频占用 cpu 的状态(忙等).
//continue;
locker.wait();
}
MyTimerTask task = queue.peek();
long curTime = System.currentTimeMillis();
if (curTime > task.getTime()) {
// 假设当前时间是 14:01, 任务时间是 14:00, 此时就
// 意味着应该要执行这个任务了.
// 需要执行任务.
queue.poll();
task.getRunnable().run();
}else {
// 让当前线程休眠一下, 按照时间差来休眠.
// Thread.sleep(task.getTime() - curTime);
locker.wait(task.getTime() - curTime);
}
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
}
}
public class Demo22 {
public static void main(String[] args) {
MyTimer timer = new MyTimer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 3");
}
}, 3000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 2");
}
}, 2000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello 1");
}
}, 1000);
System.out.println("程序开始运行");
}
}