Java ee初阶——定时器

一、定时器

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 表达式中,若涉及锁(如 synchronizedLock),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 内置的 TimerScheduledExecutorService,而是自己实现时间调度机制

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(); // 唤醒线程重新判断是否有更早的任务
        }
    }
}

逻辑解释:

  1. 一个后台线程 持续工作(while (true))。

  2. 所有任务放进优先队列,时间早的排前面。

  3. 如果队列空 → 线程 wait() 等待。

  4. 如果时间没到 → 按剩余时间继续 wait(remainingTime)

  5. 如果时间到了 → 执行 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); }
}
为什么要这样封装?
  1. 把任务和时间绑定Runnable 本身没有时间属性,包装成对象能把"任务"和"什么时候执行"绑定在一起,便于排序和管理。

  2. 实现 ComparablePriorityQueue 通过元素比较来决定队首元素。实现 compareTo 让队列始终能把最早执行的任务放在队首,便于只关注队首任务即可决定下一步动作。

  3. 提供 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<>();

设计动机
  1. 快速定位最早要执行的任务 :调度逻辑只需看队首 peek() 即可知道下一次要执行的任务,而无需遍历所有任务。

  2. 插入操作复杂度较低:插入是 O(log n),适合不极端的大量任务场景。

  3. 语义清晰:优先队列天然表达"按时间优先"的语义。

风险与考虑
  • PriorityQueue 不是线程安全 的 ------ 必须外部加锁(本例用 synchronized(locker))。

  • PriorityQueue 对重复时间、相等时间等行为是可接受的,但若需要稳定性(相同时间的先后顺序),可能需要额外字段(序号)保证一致性。

  • 如果任务量很大或需要高性能,可考虑更专业的数据结构(时间轮、HashedWheelTimer、heap 的并发版本等)。


4. 锁对象与同步:lockersynchronized

java 复制代码
private Object locker = new Object();
synchronized (locker) { ... }
locker.wait(...);
locker.notify();
为什么要同步?
  1. PriorityQueue 不是线程安全的;schedule() 由任意线程调用(外部线程),调度线程在后台访问队列。必须用同步保护队列的并发访问,避免数据结构破坏(race condition、heap 结构损坏)。

  2. 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();
        }
    }
}

逐行解释并说明为什么这样写:

  1. while (true):线程一直运行,负责不断调度任务。设计选择:常驻调度线程,直到被中断/关闭。

    • 替代:可增加退出条件(如 shutdown 标志)来优雅终止线程。
  2. synchronized (locker):在整个检查-等待-取出的过程中必须持锁,保证检查队列状态和 wait/poll 的原子性,防止竞态。

  3. while (queue.isEmpty()) locker.wait();

    • 使用 while 而非 if:防止虚假唤醒 (Java 的 wait() 可被 spuriously wake up),以及在被唤醒后需要再次检查队列是否真有任务。

    • 如果用 if,虚假唤醒会导致线程继续执行,peek() 可能返回 null,抛 NPE。

  4. MyTimerTask task = queue.peek(); --- 只读取队首但不出队。

    • 为什么不直接 poll()?因为如果时间未到不能丢弃或提前执行;peek() 允许检查时间再决定是否等待或执行。
  5. if (current < task.getTime()) locker.wait(task.getTime() - current);

    • wait(timeout) 让线程在剩余时间内等待,减少 CPU 消耗(比 busy-wait 好)。当到期或被 notify()(或被中断/虚假唤醒)返回后,循环会重新检查条件。

    • 使用 long 差值要小心负数(若系统时间变动或计算误差),要确保传给 wait 的值非负(可 Math.max(0, ...))。

  6. 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) { ... }
});

构造方法启动线程是为了:

  1. 初始化时就准备好"定时器引擎"(只启动一次)
  2. 避免每次 schedule 都重复创建线程
  3. 实现"单例后台线程"模型,所有任务共用一个执行线程
  4. 符合 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();
    }
}

总结

  • 时间轮定时器:适合高并发场景,尤其是任务数量庞大且时间粒度相对固定的情况(如分布式系统中的定时任务),通过分组批量处理任务提升效率。
  • 基于堆的定时器:实现简单,适合任务量不大、需要灵活处理任意延迟时间的场景(如单机小量定时任务),但大量任务时性能会下降。
相关推荐
飞鱼&2 小时前
HashMap相关问题详解
java·hashmap
没有bug.的程序员2 小时前
Spring Cloud Alibaba 生态总览
java·开发语言·spring boot·spring cloud·alibaba
快乐非自愿3 小时前
Java垃圾收集器全解:从Serial到G1的进化之旅
java·开发语言·python
树在风中摇曳3 小时前
Java 静态成员与继承封装实战:从报错到彻底吃透核心特性
java·开发语言
键来大师7 小时前
Android15 RK3588 修改默认不锁屏不休眠
android·java·framework·rk3588
合作小小程序员小小店8 小时前
web网页开发,在线%考试管理%系统,基于Idea,vscode,html,css,vue,java,maven,springboot,mysql
java·前端·系统架构·vue·intellij-idea·springboot
多多*9 小时前
maven常用的命令
java·log4j·maven
xie_pin_an9 小时前
MyBatis-Plus 实战:MPJLambdaWrapper 多表联查用法全解析
java·spring boot·spring·mybatis
ᐇ9599 小时前
Java LinkedList集合全面解析:双向链表的艺术与实战
java·开发语言·链表