定时器
- Timer
-
- Timer的核心方法
- [schedule vs scheduleAtFixedRate 区别](#schedule vs scheduleAtFixedRate 区别)
- 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:下一次执行时间 = 上一次实际执行结束时间 + periodscheduleAtFixedRate:下一次执行时间 = 上一次理论执行开始时间 + period
java
// 示例:假设任务执行耗时3秒,period=2秒
// schedule: 间隔 = 3 + 2 = 5秒
// scheduleAtFixedRate: 间隔 = 固定2秒(可能会出现堆积执行)
Timer的缺陷
- 单线程执行:所有任务共用一个线程,一个任务耗时过长会影响其他任务
- 异常处理糟糕:任务抛出未捕获异常会终止整个Timer线程
- 系统时间敏感:依赖系统时间,系统时间变化会影响执行
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作为锁对象,保证线程安全
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);