Netty 时间轮详解及详细源码展示
Netty 的时间轮(HashedWheelTimer
)是高性能定时任务调度的核心组件,通过环形数组和无锁化设计实现微秒级延迟任务。以下结合源码深度剖析其实现原理。
一、时间轮核心类结构
// netty-common/src/main/java/io/netty/util/HashedWheelTimer.java
public class HashedWheelTimer implements Timer {
// 时间轮核心参数
private final long tickDuration; // 每格时间(纳秒)
private final HashedWheelBucket[] wheel; // 环形数组(桶)
private final int mask; // 桶索引掩码(= ticksPerWheel - 1)
private final CountDownLatch startTimeInitialized = new CountDownLatch(1);
private volatile Thread workerThread; // Worker 线程
private final AtomicInteger workerState = new AtomicInteger(0); // 0=初始化, 1=运行, 2=关闭
// 待处理任务队列(时间轮未启动时缓存任务)
private final Queue<HashedWheelTimeout> pendingTimeouts =
new ConcurrentLinkedQueue<>();
// 构造函数
public HashedWheelTimer(
ThreadFactory threadFactory,
long tickDuration, TimeUnit unit,
int ticksPerWheel) {
this(threadFactory, tickDuration, unit, ticksPerWheel, false);
}
// 启动 Worker 线程
private void start() {
workerThread = threadFactory.newThread(worker);
workerThread.start();
}
}
二、时间轮初始化流程
2.1 核心参数计算
// 在构造函数中初始化关键参数
this.tickDuration = unit.toNanos(tickDuration);
this.ticksPerWheel = ticksPerWheel;
this.mask = this.ticksPerWheel - 1;
// 确保 ticksPerWheel 是 2 的幂次(优化取模运算)
if ((ticksPerWheel & (ticksPerWheel - 1)) != 0) {
throw new IllegalArgumentException("ticksPerWheel must be a power of two");
}
// 初始化环形数组(桶)
wheel = new HashedWheelBucket[ticksPerWheel];
for (int i = 0; i < ticksPerWheel; i++) {
wheel[i] = new HashedWheelBucket();
}
2.2 Worker 线程启动
// 启动时间轮 Worker 线程
private final Runnable worker = new Runnable() {
@Override
public void run() {
// 初始化启动时间
startTimeInitialized.countDown();
do {
final long deadline = waitForNextTick();
if (deadline > 0) {
int idx = (int) (tick & mask);
processCancelledTasks();
HashedWheelBucket bucket = wheel[idx];
transferTimeoutsToBuckets(); // 迁移延迟任务到对应桶
bucket.expireTimeouts(deadline); // 触发过期任务
tick++;
}
} while (workerState.get() == WORKER_STATE_STARTED);
// 关闭时清理资源
for (HashedWheelBucket bucket : wheel) {
bucket.clearTimeouts();
}
}
};
三、定时任务调度流程
3.1 提交任务(newTimeout
方法)
public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
long deadline = System.nanoTime() + unit.toNanos(delay);
HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);
addTimeout(timeout);
return timeout;
}
private void addTimeout(HashedWheelTimeout timeout) {
// 时间轮未启动时缓存任务到 pending 队列
if (!startTimeInitialized.await(1, TimeUnit.SECONDS)) {
pendingTimeouts.add(timeout);
return;
}
// 计算目标桶索引
int idx = (int) ((timeout.deadline / tickDuration) & mask);
HashedWheelBucket bucket = wheel[idx];
bucket.addTimeout(timeout);
}
3.2 任务桶(HashedWheelBucket
)实现
// netty-common/src/main/java/io/netty/util/HashedWheelBucket.java
static class HashedWheelBucket {
// 使用 ThreadLocal 避免伪共享(每个 CPU 缓存行独立)
private static final ThreadLocal<HashedWheelTimeout[]> TIMEOUTS_ARRAY =
ThreadLocal.withInitial(() -> new HashedWheelTimeout[1024]);
private HashedWheelTimeout[] timeouts = TIMEOUTS_ARRAY.get();
private int cursor;
void addTimeout(HashedWheelTimeout timeout) {
timeout.bucket = this;
int size = this.timeouts.length;
if (size == cursor) {
// 动态扩容(2 倍)
timeouts = Arrays.copyOf(timeouts, cursor << 1);
TIMEOUTS_ARRAY.set(timeouts);
}
timeouts[cursor++] = timeout;
}
void expireTimeouts(long deadline) {
HashedWheelTimeout[] timeouts = this.timeouts;
int cursor = this.cursor;
for (int i = 0; i < cursor; i++) {
HashedWheelTimeout timeout = timeouts[i];
if (timeout == null || timeout.state == ST_CANCELLED) {
continue;
}
if (timeout.deadline <= deadline) {
timeouts[i] = null;
if (timeout.state == ST_INIT) {
// 首次过期,执行任务
timeout.state = ST_EXPIRED;
timeout.task.run(timeout);
}
} else {
// 任务未到期,停止遍历(时间轮按顺序处理)
break;
}
}
// 清理已处理任务
System.arraycopy(timeouts, cursor, timeouts, 0, timeouts.length - cursor);
Arrays.fill(timeouts, timeouts.length - cursor, timeouts.length, null);
cursor = 0;
}
void clearTimeouts() {
Arrays.fill(timeouts, null);
cursor = 0;
}
}
四、高性能优化技术源码解析
4.1 延迟任务转移策略
// 在 Worker 线程中迁移 pending 任务到对应桶
private void transferTimeoutsToBuckets() {
for (HashedWheelTimeout timeout : pendingTimeouts) {
int idx = (int) ((timeout.deadline / tickDuration) & mask);
HashedWheelBucket bucket = wheel[idx];
bucket.addTimeout(timeout);
}
pendingTimeouts.clear();
}
4.2 线程唤醒机制
// 等待下一 tick(精准休眠)
private long waitForNextTick() {
long deadline = (tick + 1) * tickDuration;
long sleepTime = deadline - System.nanoTime();
if (sleepTime > 0) {
// 防止长时间休眠导致任务延迟
sleepTime = Math.min(sleepTime, 1_000_000_000L); // 最多休眠1秒
try {
Thread.sleep(sleepTime / 1_000_000, (int) (sleepTime % 1_000_000));
} catch (InterruptedException e) {
if (workerState.get() == WORKER_STATE_SHUTDOWN) {
return -1;
}
}
}
return deadline;
}
4.3 内存泄漏防护
// 在 HashedWheelTimeout 中使用弱引用(简化版实现)
static class HashedWheelTimeout implements Timeout {
private final WeakReference<HashedWheelTimer> timerRef;
private volatile int state = ST_INIT; // 状态:INIT, CANCELLED, EXPIRED, ACTIVE
HashedWheelTimeout(HashedWheelTimer timer, TimerTask task, long deadline) {
this.timerRef = new WeakReference<>(timer);
this.task = task;
this.deadline = deadline;
}
// 取消任务时释放资源
public boolean cancel() {
if (state != ST_INIT) {
return false;
}
state = ST_CANCELLED;
return true;
}
}
五、源码关键逻辑流程图
时间轮启动流程:
1. 初始化参数(tickDuration, ticksPerWheel)
2. 创建环形数组(HashedWheelBucket[])
3. 启动 Worker 线程
任务提交流程:
提交任务 → 计算截止时间 → 确定目标桶 → 加入 pending 队列或对应桶
Worker 线程主循环:
while (运行中) {
计算下一 tick 时间 → 休眠至该时间 → 迁移 pending 任务 → 触发过期任务 → 更新 tick
}
任务过期处理:
1. 遍历当前桶任务
2. 执行到期任务(deadline ≤ 当前时间)
3. 跳过未到期任务(保持顺序性)
六、与原生 Java 定时器的对比
特性 | HashedWheelTimer | ScheduledThreadPoolExecutor |
---|---|---|
时间复杂度 | O(1) | O(n log n) |
线程模型 | 单线程(无锁) | 多线程(锁竞争) |
内存占用 | 固定大小环形数组 | 优先队列(动态扩容) |
适用场景 | 高频短时任务(如心跳) | 低频长时任务 |
延迟精度 | 微秒级 | 毫秒级 |
七、典型应用场景源码示例
7.1 TCP 连接保活
// 创建时间轮(tick=100ms, 总周期=51.2秒)
HashedWheelTimer heartbeatTimer = new HashedWheelTimer(
Executors.defaultThreadFactory(),
100, TimeUnit.MILLISECONDS,
512
);
// 提交心跳任务
Channel channel = ...;
heartbeatTimer.newTimeout(timeout -> {
if (!channel.isActive()) {
channel.close();
} else {
channel.writeAndFlush(new PingMessage());
}
}, 30, TimeUnit.SECONDS); // 30秒未收到响应则关闭连接
7.2 超时重试机制
HashedWheelTimer retryTimer = new HashedWheelTimer(...);
int maxRetries = 3;
AtomicInteger retryCount = new AtomicInteger();
retryTimer.newTimeout(timeout -> {
if (retryCount.get() < maxRetries) {
sendRequest(); // 发送请求
retryCount.incrementAndGet();
timeout.timer().newTimeout(this, 1, TimeUnit.SECONDS); // 1秒后重试
} else {
handleFailure(); // 达到最大重试次数
}
}, 1, TimeUnit.SECONDS);
八、源码调试技巧
-
可视化时间轮状态:
- 在 IDEA 中对
HashedWheelTimer
实例打断点,观察wheel
数组中任务的分布。 - 使用条件断点过滤特定状态的任务(如
state == ST_ACTIVE
)。
- 在 IDEA 中对
-
性能分析:
- 使用
AsyncProfiler
抓取 Worker 线程的 CPU 火焰图,分析热点函数。 - 监控
pendingTimeouts
队列长度,避免任务堆积。
- 使用
-
压力测试:
- 通过
JMH
测试不同参数(ticksPerWheel
,tickDuration
)对吞吐量的影响。 - 示例 JMH 测试代码:
@BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) public class HashedWheelTimerBenchmark { @Benchmark public void testSchedule(Blackhole bh) { HashedWheelTimer timer = new HashedWheelTimer(...); timer.newTimeout(timeout -> bh.consume(timeout), 1, TimeUnit.MILLISECONDS); } }
- 通过
九、总结
Netty 时间轮通过环形数组、无锁化设计、层级扩展等核心技术,在单机场景下实现了百万级 TPS 的定时任务处理能力。其源码实现深刻体现了高性能编程的精髓:
- 用空间换时间:通过预分配环形数组避免动态内存分配。
- 用算法优化替代锁竞争:通过 CAS 和线程隔离实现无锁化。
- 用分层结构突破单层容量限制:通过层级时间轮支持大范围延迟任务。
深入理解其源码,不仅可掌握定时任务调度精髓,更能领悟到高性能系统设计的通用方法论。实际开发中,建议直接使用 Netty 原生 HashedWheelTimer
,其经过严格测试和性能优化,能满足绝大多数高并发场景需求。