【Java EE】定时器

定时器

  • Timer
  • 手撕定时器
    • MyTimer核心设计思想
    • [MyTimerTask 类 - 任务封装](#MyTimerTask 类 - 任务封装)
      • [compareTo 方法](#compareTo 方法)
    • [MyTimer 类 - 定时器核心](#MyTimer 类 - 定时器核心)
      • 数据结构
      • [schedule 方法 - 添加任务](#schedule 方法 - 添加任务)
      • [构造方法中的扫描线程 - 核心循环](#构造方法中的扫描线程 - 核心循环)
        • [(1) 为什么用 while 而不是 if判断队列空?](#(1) 为什么用 while 而不是 if判断队列空?)
        • [(2) locker.wait() 可以写成continue吗?](#(2) locker.wait() 可以写成continue吗?)
        • [(3) locker.wait() 可以写成 sleep吗?](#(3) locker.wait() 可以写成 sleep吗?)
        • [(4) 带超时的 wait](#(4) 带超时的 wait)
    • 完整测试
      • 执行流程时序图
        • [测试1: 基本执行功能](#测试1: 基本执行功能)
        • [测试2: 执行顺序验证](#测试2: 执行顺序验证)
        • [测试3: 零延迟任务](#测试3: 零延迟任务)
        • [测试4: 并发添加任务](#测试4: 并发添加任务)
        • [测试5: 空队列等待与唤醒](#测试5: 空队列等待与唤醒)
        • [测试6: 相同时间多个任务](#测试6: 相同时间多个任务)
        • [测试7: 延迟添加比队首更晚的任务](#测试7: 延迟添加比队首更晚的任务)
        • [测试8: 任务取消功能](#测试8: 任务取消功能)
        • [测试9: 较大延迟任务](#测试9: 较大延迟任务)
        • [测试10: 交错调度 - 边执行边添加](#测试10: 交错调度 - 边执行边添加)
        • 综合时序总览图
  • ScheduledThreadPoolExecutor

Timer

Timer是Java最早提供的定时任务工具,位于java.util包中,从JDK 1.3就开始存在了。

Timer的核心方法

方法 说明
schedule(TimerTask task, long delay) 延迟delay毫秒后执行一次
schedule(TimerTask task, Date time) 在指定时间点执行一次
schedule(TimerTask task, long delay, long period) 延迟执行后,以固定间隔重复执行
scheduleAtFixedRate(TimerTask task, long delay, long period) 以固定频率执行(关注执行开始时间)
java 复制代码
//Java Timer 四种调度方法示例
import java.util.Timer;
import java.util.TimerTask;
import java.util.Date;
import java.text.SimpleDateFormat;
public class TimerDemo {
    static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    public static void main(String[] args) throws Exception {
        Timer timer = new Timer();
        
        System.out.println("启动时间: " + sdf.format(new Date()));
        // ① 延迟3秒后执行一次
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("schedule(delay) 一次性执行: " + sdf.format(new Date()));
            }
        }, 3000);
        // ② 在指定时间点执行一次(5秒后)
        Date targetTime = new Date(System.currentTimeMillis() + 5000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("schedule(Date) 指定时间执行: " + sdf.format(new Date()));
            }
        }, targetTime);
        // ③ 延迟2秒后,每隔3秒重复执行(固定间隔)
        timer.schedule(new TimerTask() {
            private int count = 0;
            @Override
            public void run() {
                System.out.println("schedule(delay,period) 第" + (++count) + "次: " + sdf.format(new Date()));
                if (count >= 3) this.cancel(); // 执行3次后取消
            }
        }, 2000, 3000);
        // ④ 延迟1秒后,每隔2秒以固定频率执行
        timer.scheduleAtFixedRate(new TimerTask() {
            private int count = 0;
            @Override
            public void run() {
                System.out.println("scheduleAtFixedRate 第" + (++count) + "次: " + sdf.format(new Date()));
                if (count >= 3) {
                    this.cancel();
                    timer.cancel(); // 全部结束
                }
            }
        }, 1000, 2000);
    }
}

运行结果示例:

text 复制代码
启动时间: 15:37:57
scheduleAtFixedRate 第1次: 15:37:58
schedule(delay,period) 第1次: 15:37:59
scheduleAtFixedRate 第2次: 15:38:00
schedule(delay) 一次性执行: 15:38:00
schedule(Date) 指定时间执行: 15:38:02
scheduleAtFixedRate 第3次: 15:38:02

schedule vs scheduleAtFixedRate 区别

  • schedule:下一次执行时间 = 上一次实际执行结束时间 + period
  • scheduleAtFixedRate:下一次执行时间 = 上一次理论执行开始时间 + period
java 复制代码
// 示例:假设任务执行耗时3秒,period=2秒
// schedule: 间隔 = 3 + 2 = 5秒
// scheduleAtFixedRate: 间隔 = 固定2秒(可能会出现堆积执行)

Timer的缺陷

  1. 单线程执行:所有任务共用一个线程,一个任务耗时过长会影响其他任务
  2. 异常处理糟糕:任务抛出未捕获异常会终止整个Timer线程
  3. 系统时间敏感:依赖系统时间,系统时间变化会影响执行
java 复制代码
// Timer的致命问题示例
timer.schedule(new TimerTask() {
    public void run() {
        throw new RuntimeException("boom!"); // 这个异常会杀死整个Timer线程
    }
}, 1000);

timer.schedule(new TimerTask() {
    public void run() {
        System.out.println("我再也无法执行了"); // 永远不会执行
    }
}, 2000);

手撕定时器

MyTimer核心设计思想

代码包含两个核心类:

  • MyTimerTask:表示一个可比较的定时任务
  • MyTimer:定时器,管理任务队列并执行

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;
    }
    
    @Override
    public int compareTo(MyTimerTask o) {//关键点1
        return (int) (this.time - o.time);
    }

    public long getTime() {
        return time;
    }

    public void run() {
        task.run();
    }

compareTo 方法

java 复制代码
public int compareTo(MyTimerTask o) {
    return (int) (this.time - o.time);
}
  • 实现 Comparable 接口是为了让 PriorityQueue 能够按执行时间排序
  • this.time - o.time:时间小的排在前面(小顶堆,队首是最早要执行的任务)
  • 如果反过来写 o.time - this.time,就变成大顶堆,队首反而是最晚执行的任务

⚠️ 潜在问题:(int)(this.time - o.time) 如果两个时间差超过 int 范围会溢出。更安全的写法是 Long.compare(this.time, o.time)

MyTimer 类 - 定时器核心

java 复制代码
class MyTimer {
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private Object locker = new Object();

    public void schedule(Runnable task, long delay) {
        synchronized (locker) {
            MyTimerTask timerTask = new MyTimerTask(task, System.currentTimeMillis() + delay);
            queue.offer(timerTask);
            locker.notify();
        }
    }

    public MyTimer() {
        // 创建一个线程, 负责执行队列中的任务
        Thread t = new Thread(() -> {
            try {
                while (true) {
                    synchronized (locker) {
                        while (queue.isEmpty()) {
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        if (System.currentTimeMillis() < task.getTime()) {
                            locker.wait(task.getTime() - System.currentTimeMillis());
                        } else {
                            task.run();
                            queue.poll();
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t.start();
    }
}

数据结构

java 复制代码
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
private Object locker = new Object();
  • 优先级队列管理所有任务,队首始终是最早需要执行的任务
  • locker 作为锁对象,保证线程安全

PriorityQueue

schedule 方法 - 添加任务

java 复制代码
public void schedule(Runnable task, long delay) {
    synchronized (locker) {
        MyTimerTask timerTask = new MyTimerTask(task, System.currentTimeMillis() + delay);
        queue.offer(timerTask);
        locker.notify();
    }
}

可以写成 locker.notifyAll() 吗? 可以,而且通常更推荐

  • 这里只有一个线程在 wait,所以 notify() 没问题
  • 但如果有多个线程等待,notify() 只唤醒一个,可能唤醒的不是我们想要的
  • notifyAll() 唤醒所有等待线程,更安全,性能影响在这里可以忽略不计

构造方法中的扫描线程 - 核心循环

java 复制代码
Thread t = new Thread(() -> {
    while (true) {
        synchronized (locker) {
            // 1. 队列为空就等待
            while (queue.isEmpty()) {
                locker.wait();
            }
            
            // 2. 查看队首任务
            MyTimerTask task = queue.peek();
            
            // 3. 判断是否到了执行时间
            if (System.currentTimeMillis() < task.getTime()) {
                // 时间未到,等待剩余时间
                locker.wait(task.getTime() - System.currentTimeMillis());
            } else {
                // 时间到了,执行并移除
                task.run();
                queue.poll();
            }
        }
    }
});
(1) 为什么用 while 而不是 if判断队列空?
java 复制代码
while (queue.isEmpty()) {
    locker.wait();
}
  • wait() 可能被虚假唤醒(spurious wakeup)
  • while 保证唤醒后重新检查条件,防止空队列时取出元素
(2) locker.wait() 可以写成continue吗?
  • continue 不会释放锁,会一直循环检查,CPU 空转,这就是忙等(busy waiting)
  • wait() 会释放锁并让出 CPU,等别人 notify 后才醒来
(3) locker.wait() 可以写成 sleep吗?
  • 如果这里用 sleep(),线程不会释放锁
  • schedule 方法就无法获取锁来添加新任务,整个定时器就阻塞了
  • wait() 会释放锁,不耽误别人添加任务
(4) 带超时的 wait
java 复制代码
locker.wait(task.getTime() - System.currentTimeMillis());
  • 计算离任务执行还有多久,等待这么长时间
  • 超时后自动醒来,重新检查是否该执行任务
  • 如果等待期间有新任务插入(时间可能更早),schedule 里的 notify() 会提前唤醒

完整测试

测试覆盖的场景

测试 场景 验证点
1 基本执行 单任务能否按时执行
2 执行顺序 不同延迟的任务是否按时间排序
3 零延迟 delay=0 边界情况
4 并发添加 多线程同时schedule,线程安全
5 空队列等待 wait/notify 正确唤醒
6 相同时间 同时间多任务的执行
7 插入更早任务 后添加但更紧急的任务能否插队
8 任务取消 扩展功能,取消未执行的任务
9 大延迟 较长时间的wait准确性
10 交错调度 任务执行过程中添加新任务
java 复制代码
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class MyTimerTest {
    
    // 记录测试结果
    private static int passed = 0;
    private static int failed = 0;
    
    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== MyTimer 综合测试 ==========\n");
        
        testBasicExecution();
        Thread.sleep(500);  // 测试间隔
        
        testExecutionOrder();
        Thread.sleep(500);
        
        testZeroDelay();
        Thread.sleep(500);
        
        testConcurrentSchedule();
        Thread.sleep(500);
        
        testEmptyQueueWait();
        Thread.sleep(500);
        
        testMultipleTasksSameTime();
        Thread.sleep(500);
        
        testLateSchedule();
        Thread.sleep(500);
        
        testCancelTask();  // 这里实现了一个可取消的版本
        Thread.sleep(500);
        
        testLargeDelay();
        Thread.sleep(500);
        
        testInterleavedSchedule();
        Thread.sleep(500);
        
        System.out.println("\n========== 测试结果汇总 ==========");
        System.out.println("通过: " + passed + " | 失败: " + failed);
        System.out.println("总计: " + (passed + failed));
    }
    
    // ==================== 辅助方法 ====================
    
    private static void assertTrue(boolean condition, String testName, String message) {
        if (condition) {
            System.out.println("✓ " + testName + " - 通过");
            passed++;
        } else {
            System.out.println("✗ " + testName + " - 失败: " + message);
            failed++;
        }
    }
    
    private static void assertTimeInRange(long actual, long expected, long tolerance, String testName) {
        long diff = Math.abs(actual - expected);
        boolean ok = diff <= tolerance;
        if (ok) {
            System.out.println("✓ " + testName + " - 通过 (偏差: " + diff + "ms)");
            passed++;
        } else {
            System.out.println("✗ " + testName + " - 失败: 预期~" + expected + 
                             "ms, 实际" + actual + "ms, 偏差" + diff + "ms");
            failed++;
        }
    }
    
    // ==================== 测试案例 ====================
    
    /**
     * 测试1: 基本功能 - 任务能否正常执行
     */
    static void testBasicExecution() throws InterruptedException {
        System.out.println("--- 测试1: 基本执行功能 ---");
        
        CountDownLatch latch = new CountDownLatch(1);
        MyTimer timer = new MyTimer();
        long start = System.currentTimeMillis();
        long[] actualTime = new long[1];
        
        timer.schedule(() -> {
            actualTime[0] = System.currentTimeMillis();
            latch.countDown();
        }, 1000);
        
        latch.await();  // 等待任务执行
        long elapsed = actualTime[0] - start;
        
        assertTimeInRange(elapsed, 1000, 200, "任务在约1000ms后执行");
    }
    
    /**
     * 测试2: 执行顺序 - 多个任务是否按时间顺序执行
     */
    static void testExecutionOrder() throws InterruptedException {
        System.out.println("\n--- 测试2: 执行顺序验证 ---");
        
        CountDownLatch latch = new CountDownLatch(3);
        MyTimer timer = new MyTimer();
        StringBuilder executionOrder = new StringBuilder();
        
        // 按倒序添加,但应该按正序执行
        timer.schedule(() -> {
            executionOrder.append("C");
            latch.countDown();
        }, 300);
        
        timer.schedule(() -> {
            executionOrder.append("A");
            latch.countDown();
        }, 100);
        
        timer.schedule(() -> {
            executionOrder.append("B");
            latch.countDown();
        }, 200);
        
        latch.await();
        
        assertTrue("ABC".equals(executionOrder.toString()), 
                  "执行顺序为ABC", 
                  "实际顺序: " + executionOrder.toString());
    }
    
    /**
     * 测试3: 零延迟 - delay=0 的任务
     */
    static void testZeroDelay() throws InterruptedException {
        System.out.println("\n--- 测试3: 零延迟任务 ---");
        
        CountDownLatch latch = new CountDownLatch(1);
        MyTimer timer = new MyTimer();
        long start = System.currentTimeMillis();
        long[] actualTime = new long[1];
        
        timer.schedule(() -> {
            actualTime[0] = System.currentTimeMillis();
            latch.countDown();
        }, 0);
        
        latch.await();
        long elapsed = actualTime[0] - start;
        
        assertTimeInRange(elapsed, 0, 100, "零延迟任务几乎立即执行");
    }
    
    /**
     * 测试4: 并发添加任务
     */
    static void testConcurrentSchedule() throws InterruptedException {
        System.out.println("\n--- 测试4: 并发添加任务 ---");
        
        int taskCount = 20;
        CountDownLatch latch = new CountDownLatch(taskCount);
        MyTimer timer = new MyTimer();
        AtomicInteger counter = new AtomicInteger(0);
        
        // 多个线程同时添加任务
        for (int i = 0; i < taskCount; i++) {
            final int delay = (int)(Math.random() * 500);
            new Thread(() -> {
                timer.schedule(() -> {
                    counter.incrementAndGet();
                    latch.countDown();
                }, delay);
            }).start();
        }
        
        latch.await();  // 等待所有任务完成
        
        assertTrue(counter.get() == taskCount, 
                  "并发添加" + taskCount + "个任务全部执行", 
                  "实际执行: " + counter.get());
    }
    
    /**
     * 测试5: 空队列等待 - schedule延迟调用,验证扫描线程正确等待
     */
    static void testEmptyQueueWait() throws InterruptedException {
        System.out.println("\n--- 测试5: 空队列等待与唤醒 ---");
        
        CountDownLatch latch = new CountDownLatch(1);
        MyTimer timer = new MyTimer();
        long[] actualTime = new long[1];
        
        // 先让定时器空转500ms,然后再添加任务
        Thread.sleep(500);
        
        long start = System.currentTimeMillis();
        timer.schedule(() -> {
            actualTime[0] = System.currentTimeMillis();
            latch.countDown();
        }, 200);
        
        latch.await();
        long elapsed = actualTime[0] - start;
        
        assertTimeInRange(elapsed, 200, 100, "空队列后添加任务正常执行");
    }
    
    /**
     * 测试6: 同一时间多个任务
     */
    static void testMultipleTasksSameTime() throws InterruptedException {
        System.out.println("\n--- 测试6: 相同时间多个任务 ---");
        
        CountDownLatch latch = new CountDownLatch(3);
        MyTimer timer = new MyTimer();
        AtomicInteger counter = new AtomicInteger(0);
        long start = System.currentTimeMillis();
        long[] timestamps = new long[3];
        
        timer.schedule(() -> {
            timestamps[0] = System.currentTimeMillis();
            counter.incrementAndGet();
            latch.countDown();
        }, 500);
        
        timer.schedule(() -> {
            timestamps[1] = System.currentTimeMillis();
            counter.incrementAndGet();
            latch.countDown();
        }, 500);
        
        timer.schedule(() -> {
            timestamps[2] = System.currentTimeMillis();
            counter.incrementAndGet();
            latch.countDown();
        }, 500);
        
        latch.await();
        
        // 三个任务都应该在~500ms后执行
        boolean allInTime = true;
        for (int i = 0; i < 3; i++) {
            long elapsed = timestamps[i] - start;
            if (Math.abs(elapsed - 500) > 200) {
                allInTime = false;
                break;
            }
        }
        
        assertTrue(counter.get() == 3 && allInTime, 
                  "相同时间的3个任务全部在预期时间附近执行", 
                  "执行数: " + counter.get());
    }
    
    /**
     * 测试7: 延迟添加任务(比当前队首任务更晚的时间)
     */
    static void testLateSchedule() throws InterruptedException {
        System.out.println("\n--- 测试7: 延迟添加比队首更晚的任务 ---");
        
        CountDownLatch latch = new CountDownLatch(2);
        MyTimer timer = new MyTimer();
        long start = System.currentTimeMillis();
        long[] earlyTime = new long[1];
        long[] lateTime = new long[1];
        
        // 先添加一个1000ms后执行的任务
        timer.schedule(() -> {
            earlyTime[0] = System.currentTimeMillis();
            latch.countDown();
        }, 1000);
        
        // 等200ms后,再添加一个500ms后执行的任务(总时间700ms,比1000ms早)
        Thread.sleep(200);
        timer.schedule(() -> {
            lateTime[0] = System.currentTimeMillis();
            latch.countDown();
        }, 500);
        
        latch.await();
        
        // 后添加的任务应该先执行(因为它在700ms时到期,比1000ms早)
        long lateElapsed = lateTime[0] - start;
        long earlyElapsed = earlyTime[0] - start;
        
        boolean lateFirst = lateTime[0] < earlyTime[0];
        
        assertTrue(lateFirst, 
                  "后添加但时间更早的任务先执行", 
                  "后添加任务在+" + lateElapsed + "ms执行, 先添加任务在+" + earlyElapsed + "ms执行");
    }
    
    /**
     * 测试8: 可取消任务(扩展功能)
     */
    static void testCancelTask() throws InterruptedException {
        System.out.println("\n--- 测试8: 任务取消功能 ---");
        
        // 需要扩展MyTimerTask添加取消功能
        CountDownLatch executedLatch = new CountDownLatch(1);
        CountDownLatch notExecutedLatch = new CountDownLatch(1);
        
        MyTimerWithCancel timer = new MyTimerWithCancel();
        
        // 添加一个正常任务
        timer.schedule(() -> {
            executedLatch.countDown();
        }, 200);
        
        // 添加一个会被取消的任务
        MyTimerTaskWithCancel cancelTask = timer.schedule(() -> {
            notExecutedLatch.countDown();  // 不应该执行
        }, 400);
        
        // 在150ms后取消第二个任务
        Thread.sleep(150);
        cancelTask.cancel();
        
        // 等待600ms
        Thread.sleep(600);
        
        assertTrue(executedLatch.getCount() == 0, 
                  "未取消的任务正常执行", 
                  "");
        assertTrue(notExecutedLatch.getCount() == 1, 
                  "已取消的任务未执行", 
                  "");
    }
    
    /**
     * 测试9: 大幅度延迟
     */
    static void testLargeDelay() throws InterruptedException {
        System.out.println("\n--- 测试9: 较大延迟任务 ---");
        
        CountDownLatch latch = new CountDownLatch(1);
        MyTimer timer = new MyTimer();
        long start = System.currentTimeMillis();
        long[] actualTime = new long[1];
        
        timer.schedule(() -> {
            actualTime[0] = System.currentTimeMillis();
            latch.countDown();
        }, 2000);
        
        latch.await();
        long elapsed = actualTime[0] - start;
        
        assertTimeInRange(elapsed, 2000, 300, "2000ms延迟任务正常执行");
    }
    
    /**
     * 测试10: 交错调度 - 边执行边添加
     */
    static void testInterleavedSchedule() throws InterruptedException {
        System.out.println("\n--- 测试10: 边执行边添加任务 ---");
        
        CountDownLatch allDone = new CountDownLatch(4);
        MyTimer timer = new MyTimer();
        AtomicInteger counter = new AtomicInteger(0);
        StringBuilder order = new StringBuilder();
        
        // 任务1: 在300ms执行,并添加任务3
        timer.schedule(() -> {
            order.append("1");
            counter.incrementAndGet();
            allDone.countDown();
            
            // 在执行时添加新任务
            timer.schedule(() -> {
                order.append("3");
                counter.incrementAndGet();
                allDone.countDown();
            }, 500);  // 从任务1执行时间起+500ms
        }, 300);
        
        // 任务2: 在600ms执行
        timer.schedule(() -> {
            order.append("2");
            counter.incrementAndGet();
            allDone.countDown();
        }, 600);
        
        // 主线程等800ms后添加任务4
        Thread.sleep(800);
        timer.schedule(() -> {
            order.append("4");
            counter.incrementAndGet();
            allDone.countDown();
        }, 100);
        
        allDone.await();
        
        assertTrue(counter.get() == 4, 
                  "交错调度的4个任务全部执行", 
                  "执行数: " + counter.get() + ", 顺序: " + order.toString());
    }
}

// ==================== 带取消功能的扩展(测试8使用) ====================

class MyTimerTaskWithCancel implements Comparable<MyTimerTaskWithCancel> {
    private Runnable task;
    private long time;
    private volatile boolean cancelled = false;
    
    public MyTimerTaskWithCancel(Runnable task, long time) {
        this.task = task;
        this.time = time;
    }
    
    public void cancel() {
        cancelled = true;
    }
    
    public boolean isCancelled() {
        return cancelled;
    }
    
    @Override
    public int compareTo(MyTimerTaskWithCancel o) {
        return Long.compare(this.time, o.time);
    }
    
    public long getTime() {
        return time;
    }
    
    public void run() {
        if (!cancelled) {
            task.run();
        }
    }
}

class MyTimerWithCancel {
    private PriorityQueue<MyTimerTaskWithCancel> queue = new PriorityQueue<>();
    private Object locker = new Object();
    
    public MyTimerTaskWithCancel schedule(Runnable task, long delay) {
        synchronized (locker) {
            MyTimerTaskWithCancel timerTask = new MyTimerTaskWithCancel(
                task, System.currentTimeMillis() + delay
            );
            queue.offer(timerTask);
            locker.notify();
            return timerTask;  // 返回任务引用,用于取消
        }
    }
    
    public MyTimerWithCancel() {
        Thread t = new Thread(() -> {
            try {
                while (true) {
                    synchronized (locker) {
                        while (queue.isEmpty()) {
                            locker.wait();
                        }
                        
                        MyTimerTaskWithCancel task = queue.peek();
                        
                        if (task.isCancelled()) {
                            queue.poll();  // 跳过已取消的任务
                            continue;
                        }
                        
                        long currentTime = System.currentTimeMillis();
                        if (currentTime < task.getTime()) {
                            locker.wait(task.getTime() - currentTime);
                        } else {
                            task.run();
                            queue.poll();
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t.start();
    }
}

预期输出示例

复制代码
========== MyTimer 综合测试 ==========

--- 测试1: 基本执行功能 ---
✓ 任务在约1000ms后执行 - 通过 (偏差: 1ms)

--- 测试2: 执行顺序验证 ---
✓ 执行顺序为ABC - 通过

--- 测试3: 零延迟任务 ---
✓ 零延迟任务几乎立即执行 - 通过 (偏差: 0ms)

--- 测试4: 并发添加任务 ---
✓ 并发添加20个任务全部执行 - 通过

--- 测试5: 空队列等待与唤醒 ---
✓ 空队列后添加任务正常执行 - 通过 (偏差: 7ms)

--- 测试6: 相同时间多个任务 ---
✓ 相同时间的3个任务全部在预期时间附近执行 - 通过

--- 测试7: 延迟添加比队首更晚的任务 ---
✓ 后添加但时间更早的任务先执行 - 通过

--- 测试8: 任务取消功能 ---
✓ 未取消的任务正常执行 - 通过
✓ 已取消的任务未执行 - 通过

--- 测试9: 较大延迟任务 ---
✓ 2000ms延迟任务正常执行 - 通过 (偏差: 9ms)

--- 测试10: 边执行边添加任务 ---
✓ 交错调度的4个任务全部执行 - 通过

========== 测试结果汇总 ==========
通过: 11 | 失败: 0
总计: 11

执行流程时序图

测试1: 基本执行功能
复制代码
时间轴(ms)    主线程                        扫描线程                      队列状态
─────────────────────────────────────────────────────────────────────────────
0            new MyTimer()                线程启动                       []
             schedule(R, 1000)            获取锁                         
                                          wait() ← 队列为空              []
             添加任务(执行时刻=1000)       被notify唤醒                    [1000:R]
             locker.notify()              
                                          peek() → 1000                 [1000:R]
                                          当前时间0 < 1000              
                                          wait(1000)                    [1000:R]
             latch.await() ← 阻塞等待     
                                        
1000                                      wait超时自动醒来               [1000:R]
                                          当前时间≈1000 ≥ 1000          
                                          task.run() → R执行            [1000:R]
                                          actualTime[0]=1000            
                                          queue.poll() → 清空           []
                                          latch.countDown()             
                                          while(true)循环                []
                                          wait() ← 再次等待             []
                                          
              latch.await()返回           
              elapsed = 1000 - 0 = 1000  
              ✓ 通过 (偏差~几ms)
测试2: 执行顺序验证
复制代码
时间轴(ms)    主线程                        扫描线程                      队列(按时间排序)
─────────────────────────────────────────────────────────────────────────────
0            schedule(C, 300)                                            [300:C]
             schedule(A, 100)                                            [100:A, 300:C]  ← A排到队首!
             schedule(B, 200)                                            [100:A, 200:B, 300:C]
             latch.await()                                              
                                          
               (扫描线程已在运行)          
             获取锁                                                      
             peek() → A(time=100)                                       [100:A, 200:B, 300:C]
             当前时间0 < 100                                            
             wait(100)                                                   
                                          
100          wait超时醒来                                                
             当前时间≈100 ≥ 100                                         
             A.run() → executionOrder="A"                               
             queue.poll() → 移除A                                       [200:B, 300:C]
             latch.countDown() (1/3)                                    
             peek() → B(time=200)                                       
             当前时间≈100 < 200                                         
             wait(100)                                                   
                                          
200          wait超时醒来                                                
             B.run() → executionOrder="AB"                              
             queue.poll() → 移除B                                       [300:C]
             latch.countDown() (2/3)                                    
             peek() → C(time=300)                                       
             当前时间≈200 < 300                                         
             wait(100)                                                   
                                          
300          wait超时醒来                                                
             C.run() → executionOrder="ABC"                             
             queue.poll() → 清空                                        []
             latch.countDown() (3/3)                                    
             wait() ← 等待新任务                                        
                                          
              latch.await()返回                                         
              "ABC".equals("ABC") → true                                
              ✓ 通过

虽然添加顺序是 C→A→B,但PriorityQueue自动按时间排序,执行顺序变成 A→B→C。

测试3: 零延迟任务
复制代码
时间轴(ms)    主线程                        扫描线程                      队列
─────────────────────────────────────────────────────────────────────────────
0            schedule(R, 0)                                              
                                          wait() ← 队列为空              []
             添加任务(执行时刻=0+0=0)                                    
             locker.notify()                                            
                                          wait被唤醒                     [0:R]
                                          peek() → time=0               [0:R]
                                          当前时间≈0 ≥ 0                
                                          wait(0) ← 立即返回!            
                                          R.run()                       
                                          queue.poll() → 清空           []
                                          
              elapsed ≈ 几ms                                             
              ✓ 通过 (偏差<100ms)

delay=0时,System.currentTimeMillis() + 0 约等于当前时间,所以 wait(0) 几乎立即返回。

测试4: 并发添加任务
复制代码
时间轴(ms)    线程1         线程2         线程3         ...线程20      扫描线程              队列
─────────────────────────────────────────────────────────────────────────────────────────────
0            创建timer                                                                      []
             
1            schedule       schedule      schedule     schedule
            (R1, 23ms)    (R2, 15ms)   (R3, 8ms)   (R20, 47ms)
             
           线程1获取锁    线程2等待锁   线程3等待锁  线程20等待锁                             
           offer(R1)                                                                        [23:R1]
           notify()                                                                         
           释放锁                                                                           
                         线程2获取锁                                                        
                         offer(R2)                                                          [15:R2, 23:R1]  ← R2排前面
                         notify()                                                           
                         释放锁                                                             
                                      线程3获取锁                                            
                                      offer(R3)                                              [8:R3, 23:R1, 15:R2]  ← R3排最前
                                      notify()                                              
                                      释放锁                                                 
             
                                                                     ...线程20获取锁
                                                                     offer(R20)              [8:R3, 15:R2, 23:R1, 47:R20]
                                                                     notify()
             
                                          peek() → R3(time=8)                                
                                          当前时间≈5 < 8                                     
                                          wait(3)                                            
             
8                                         wait超时醒来                                       
                                          R3.run() → counter=1                              
                                          poll() → 移除R3                                    
                                          peek() → R2(time=15)                              
                                          当前时间≈10 < 15                                  
                                          wait(5)                                            
             
15                                        wait超时醒来                                       
                                          R2.run() → counter=2                              
                                          poll() → 移除R2                                    
                                          ...                                               
                                          
...                                      所有任务依次执行                                     
             
约50                                     counter = 20                                       
                                         latch.countDown() 全部完成                          
             
            latch.await()返回                                                              
            counter.get() == 20 → true                                                      
            ✓ 通过

多个线程竞争同一把锁,synchronized保证了线程安全。

测试5: 空队列等待与唤醒
复制代码
时间轴(ms)    主线程                        扫描线程                      队列
─────────────────────────────────────────────────────────────────────────────
0            new MyTimer()                线程启动                       []
                                          while(true)                    
                                          获取锁                          
                                          while(队列空) → true            
                                          wait() ← 阻塞等待              []
                                          (释放锁, CPU休眠)
             
500           sleep(500)结束              
             (扫描线程仍在wait)                                          
             schedule(R, 200)             获取锁                          
             添加任务(执行时刻=500+200=700)                              
             locker.notify()              被唤醒!                        [700:R]
             latch.await()                当前时间≈500 < 700             
                                          wait(200)                      [700:R]
             
700                                        wait超时醒来                   [700:R]
                                          当前时间≈700 ≥ 700             
                                          R.run()                        
                                          queue.poll() → 清空            []
                                          latch.countDown()              
                                          while(队列空) → true            
                                          wait() ← 再次等待              []
             
              latch.await()返回                                          
              elapsed = 200                                               
              ✓ 通过

即使先创建了空定时器,后续添加任务也能通过notify()正确唤醒扫描线程。

测试6: 相同时间多个任务
复制代码
时间轴(ms)    主线程                        扫描线程                      队列(按时间分组)
─────────────────────────────────────────────────────────────────────────────
0            schedule(R1, 500)                                           [500:R1]
             schedule(R2, 500)                                           [500:R1, 500:R2]
             schedule(R3, 500)                                           [500:R1, 500:R2, 500:R3]
                                                                         (相同时间的顺序由PriorityQueue实现决定)
             latch.await()                                              
                                          
             扫描线程运行中                                              
             peek() → R1(time=500)                                      
             当前时间0 < 500                                             
             wait(500)                                                   
                                          
500          wait超时醒来                                                
             当前时间≈500 ≥ 500                                          
             R1.run() → timestamps[0]=500                               
             queue.poll() → 移除R1                                      [500:R2, 500:R3]
             latch.countDown() (1/3)                                    
             peek() → R2(time=500)                                      
             当前时间≈500 ≥ 500  ← 立即判断                              
             R2.run() → timestamps[1]=500                               
             queue.poll() → 移除R2                                      [500:R3]
             latch.countDown() (2/3)                                    
             peek() → R3(time=500)                                      
             当前时间≈500 ≥ 500                                          
             R3.run() → timestamps[2]=500                               
             queue.poll() → 清空                                        []
             latch.countDown() (3/3)                                    
             wait() ← 等待新任务                                        
                                          
              latch.await()返回                                          
              三个时间戳都≈500                                           
              counter==3 → true                                          
              ✓ 通过

相同时间的任务会"连续出队",第一个执行完后poll(),第二个立刻变成队首,发现时间也到了就继续执行。

测试7: 延迟添加比队首更晚的任务
复制代码
时间轴(ms)    主线程                        扫描线程                      队列
─────────────────────────────────────────────────────────────────────────────
0            schedule(R1, 1000)                                          
             添加任务(执行时刻=1000)                                     
                                          peek() → R1(time=1000)         [1000:R1]
                                          wait(1000)  ← 要等很久         
             
             schedule(R2, 500) ← 主线程
             继续运行! 等200ms
                                          
200          sleep(200)结束              (扫描线程还在wait)              
             schedule(R2, 500)                                           [1000:R1]
             添加任务(执行时刻=200+500=700)                              
             locker.notify()                                             
                                          wait被提前唤醒!                 
                                          重新检查队列                   
                                          peek() → R2(time=700)  ← R2插队成功!
                                          [700:R2, 1000:R1]
                                          当前时间≈200 < 700             
                                          wait(700-200=500)              
             latch.await()                                               
                                          
700                                        wait超时醒来                   
                                          R2.run() → lateTime[0]=700     
                                          queue.poll() → 移除R2          [1000:R1]
                                          latch.countDown() (1/2)        
                                          peek() → R1(time=1000)         
                                          当前时间≈700 < 1000            
                                          wait(300)                      
                                          
1000                                       wait超时醒来                   
                                          R1.run() → earlyTime[0]=1000    
                                          queue.poll() → 清空            []
                                          latch.countDown() (2/2)        
                                          
              latch.await()返回                                          
              lateTime(700) < earlyTime(1000)                            
              ✓ 通过 (后添加的R2先执行了!)

这是locker.notify()最关键的作用------打断wait,让扫描线程重新检查队首。如果没有notify,R2要等1000ms后R1执行完才能被发现。

测试8: 任务取消功能
复制代码
时间轴(ms)    主线程                        扫描线程                      队列
─────────────────────────────────────────────────────────────────────────────
0            schedule(R_normal, 200)                                     
             返回 taskRef_normal                                         
             schedule(R_cancel, 400)                                     
             返回 taskRef_cancel                                         
                                          peek() → normal(time=200)      [200:normal, 400:cancel]
                                          wait(200)                      
             
150           sleep(150)结束             (扫描线程还在wait)              
             taskRef_cancel.cancel()                                     
             cancelled = true  ← 标记                                    
                                          
200                                        wait超时醒来                   
                                          peek() → normal(time=200)      [200:normal, 400:cancel]
                                          时间到了                       
                                          normal.run()                   
                                          未被取消, 正常执行             
                                          queue.poll() → 移除normal      [400:cancel]
                                          latch_executed.countDown()     
                                          peek() → cancel(time=400)      [400:cancel]
                                          cancel.isCancelled() → true!   [400:cancel]
                                          queue.poll() → 移除并丢弃!     []
                                          continue ← 不执行, 继续循环    
                                          wait()                         
                                          
600           sleep(600)结束                                            
              
              检查结果:
              latch_executed = 0 ✓ (执行了)
              latch_notExecuted = 1 ✓ (没执行)
              ✓ 通过

取消任务的检查发生在扫描线程peek()之后。被取消的任务虽然还留在队首,但被识别后直接poll()丢弃。

测试9: 较大延迟任务
复制代码
时间轴(ms)    主线程                        扫描线程                      队列
─────────────────────────────────────────────────────────────────────────────
0            schedule(R, 2000)                                           
                                          peek() → R(time=2000)          [2000:R]
                                          wait(2000)  ← 长时间wait      
             latch.await()                                               
                                          
             主线程阻塞等待...                                           
             
             (漫长的2000ms...)                                           
             
2000                                      wait超时醒来                   
                                          R.run()                        
                                          actualTime[0]=2000             
                                          queue.poll() → 清空            []
                                          latch.countDown()              
                                          
              latch.await()返回                                          
              elapsed ≈ 2000                                             
              ✓ 通过 (偏差<300ms)

wait(2000)会等待约2秒,验证长时间wait的准确性。

测试10: 交错调度 - 边执行边添加
复制代码
时间轴(ms)    主线程                    扫描线程                    任务1内部        队列
─────────────────────────────────────────────────────────────────────────────────────────
0            schedule(R1, 300)                                     [300:R1]
             schedule(R2, 600)                                     [300:R1, 600:R2]
             latch.await()                                         
                                    peek() → R1(time=300)          
                                    wait(300)                      
                                                                  
300                                  wait超时醒来                   
                                    R1.run()                       
                                                    order="1"     
                                                    counter=1     
                                                    countDown(1/4)
                                                    ↓ 在R1内部schedule!
                                                    timer.schedule(R3, 500)
                                                    执行时刻=300+500=800
                                                    offer(R3)       [600:R2, 800:R3]
                                                    notify() ← 但扫描线程没在wait
                                    R1.run()返回                   
                                    queue.poll() → 移除R1          [600:R2, 800:R3]
                                    peek() → R2(time=600)          
                                    wait(300)                      
                                                                  
600                                  wait超时醒来                   
                                    R2.run() → order="12"         
                                    countDown(2/4)                
                                    queue.poll() → 移除R2          [800:R3]
                                    peek() → R3(time=800)          
                                    wait(200)                      
                                                                  
800           主线程sleep(800)结束   wait超时醒来                   
             schedule(R4, 100)       R3.run() → order="123"       
             执行时刻=800+100=900   countDown(3/4)                
             offer(R4)              queue.poll() → 移除R3          
                                    peek() → R4(time=900)  ← R4!  [900:R4]
                                    wait(100)                      
                                                                  
900                                  wait超时醒来                   
                                    R4.run() → order="1234"       
                                    countDown(4/4)                
                                    queue.poll() → 清空            []
                                    wait()                         
                                                                  
              latch.await()返回                                    
              counter=4, order="1234"                              
              ✓ 通过

任务执行过程中可以安全地添加新任务。因为schedule和扫描线程用的是同一把锁,但在R1.run()执行时,锁并没有释放------所以R3实际是通过重入(同一线程)添加的。实际上这里有个微妙的问题:扫描线程在持有锁的情况下执行task.run(),而task.run()内部又调用schedule ,由于synchronized是可重入锁,所以不会死锁。R3被添加后,不影响当前正在执行的R1,R1结束后扫描线程继续处理队列。

综合时序总览图
复制代码
                         测试覆盖的场景全景
                         
时间 ───────────────────────────────────────────────────────►

测试1:  [────wait(1000)────]●执行    ← 基本功能
测试2:  [─A─][─B─][─C─]              ← 顺序保证  
测试3:  ●立即执行                     ← 零延迟
测试4:  ★★★★★★ 并发安全              ← 多线程
测试5:  [等待]......[唤醒]●           ← 空队列恢复
测试6:  [等待500]●●●                  ← 同时间批量
测试7:  [─R2先─]....[─R1后─]          ← 插队
测试8:  [─正常●─]..[─取消✗─]          ← 取消机制
测试9:  [───────wait(2000)───────]●  ← 长延迟
测试10: [─R1─]..[─R2─]..[─R3─][─R4─] ← 动态添加

图例: ●=任务执行  ★=并发操作  []=等待

ScheduledThreadPoolExecutor

鉴于Timer的各种缺陷,JDK 1.5引入了ScheduledThreadPoolExecutor,它是ThreadPoolExecutor的扩展是一个带有线程池的定时器,完美解决了Timer的问题。

基本使用

java 复制代码
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3);
        
        // 延迟2秒执行,只执行一次
        scheduler.schedule(() -> {
            System.out.println("单次执行任务");
        }, 2, TimeUnit.SECONDS);
        
        // 延迟1秒后,每3秒执行一次(固定间隔,等价于Timer的schedule)
        scheduler.scheduleWithFixedDelay(() -> {
            System.out.println("固定间隔执行");
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
        }, 1, 3, TimeUnit.SECONDS);
        
        // 延迟1秒后,每2秒执行一次(固定频率,等价于Timer的scheduleAtFixedRate)
        scheduler.scheduleAtFixedRate(() -> {
            System.out.println("固定频率执行");
        }, 1, 2, TimeUnit.SECONDS);
    }
}

ScheduledThreadPoolExecutor的优势

特性 Timer ScheduledThreadPoolExecutor
线程模型 单线程 多线程池
异常影响 整个Timer停止 只影响当前任务
任务堆积 可能延迟 多线程并发处理
系统时间依赖 否(使用相对时间)
资源管理 只能cancel停止 优雅shutdown
java 复制代码
// 异常隔离示例
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);

pool.scheduleAtFixedRate(() -> {
    throw new RuntimeException("任务1失败");
}, 0, 1, TimeUnit.SECONDS);

pool.scheduleAtFixedRate(() -> {
    System.out.println("任务2正常执行"); // 不会受影响,继续执行
}, 0, 1, TimeUnit.SECONDS);
相关推荐
a7963lin1 小时前
html标签怎样表示搜索框_input type=search语义优化【操作】
jvm·数据库·python
小德乐乐1 小时前
AlphaFold3 预测蛋白结构 分子互作分析出图 代理计算 本地部署 可指导
图像处理·python
a7963lin1 小时前
Python数据分析如何识别异常值_IQR四分位距检测法实战
jvm·数据库·python
Fuly10241 小时前
java面试知识点复习
java·开发语言·面试
m0_613856291 小时前
如何解决宝塔面板Web端文件管理器打开目录时反应极其缓慢
jvm·数据库·python
wltx16881 小时前
独立站搭建需要做氨氮检测仪展示吗?
人工智能·python
The_superstar61 小时前
衡山派D133EBS入门笔记
笔记·python·c·衡山派·小曹越
Air_July1 小时前
Brower User Web UI部署详细步骤
人工智能·python·测试工具
m0_613856292 小时前
mysql如何优化重复索引_mysql冗余索引查找与处理
jvm·数据库·python