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

总结

  • 时间轮定时器:适合高并发场景,尤其是任务数量庞大且时间粒度相对固定的情况(如分布式系统中的定时任务),通过分组批量处理任务提升效率。
  • 基于堆的定时器:实现简单,适合任务量不大、需要灵活处理任意延迟时间的场景(如单机小量定时任务),但大量任务时性能会下降。
相关推荐
程序员小假13 小时前
设计一个支持万人同时抢购商品的秒杀系统?
java·后端
L***d67013 小时前
Spring Boot(七):Swagger 接口文档
java·spring boot·后端
C雨后彩虹13 小时前
竖直四子棋
java·数据结构·算法·华为·面试
疾风sxp13 小时前
nl2sql技术实现自动sql生成之langchain4j SqlDatabaseContentRetriever
java·人工智能·langchain4j
一勺菠萝丶14 小时前
PDF24 转图片出现“中间横线”的根本原因与终极解决方案(DPI 原理详解)
java
姓蔡小朋友14 小时前
Unsafe类
java
一只专注api接口开发的技术猿14 小时前
如何处理淘宝 API 的请求限流与数据缓存策略
java·大数据·开发语言·数据库·spring
荒诞硬汉14 小时前
对象数组.
java·数据结构
期待のcode14 小时前
Java虚拟机的非堆内存
java·开发语言·jvm
黎雁·泠崖14 小时前
Java入门篇之吃透基础语法(二):变量全解析(进制+数据类型+键盘录入)
java·开发语言·intellij-idea·intellij idea