【Netty源码解读和权威指南】第35篇:Netty时间轮HashedWheelTimer源码解析——百万定时任务的秘密

【Netty源码解读和权威指南】第35篇:Netty时间轮HashedWheelTimer源码解析——百万定时任务的秘密

上一篇【第34篇】Netty Selector优化——为什么比JDK NIO快这么多
下一篇【第36篇】Netty时间轮高级应用——10亿级定时任务的工程实践


一、为什么需要时间轮?

方案10万定时任务性能问题
JDK Timer极差单线程,插入O(nlogn)
ScheduledThreadPool一般堆插入O(logn),内存大
HashedWheelTimer优秀插入O(1),内存小

二、时间轮数据结构

tickDuration=100ms, ticksPerWheel=8 ┌───┬───┬───┬───┬───┬───┬───┬───┐ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ ← 8个槽位 └───┴───┴───┴───┴───┴───┴───┴───┘ ↑ ↑ 指针(每100ms移动一格) 每个槽位是链表 时间轮周期 = 8 × 100ms = 800ms 指针指向槽0:执行该槽链表中的所有任务 指针指向槽1:执行该槽链表中的所有任务 ...

三、核心源码

publicclassHashedWheelTimerimplementsTimer{privatefinalWorkerworker=newWorker();privatefinalHashedWheelBucket[]wheel;// 环形数组privatefinalintmask;// wheel.length - 1,用于取模privatefinallongtickDuration;// 每格时间间隔// 添加定时任务 O(1)publicTimeoutnewTimeout(TimerTasktask,longdelay,TimeUnitunit){longdeadline=System.nanoTime()+unit.toNanos(delay)-startTime;HashedWheelTimeouttimeout=newHashedWheelTimeout(this,task,deadline);// 计算槽位longcalculated=deadline/tickDuration;timeout.remainingRounds=(calculated-tick)/wheel.length;intstopIndex=(int)(calculated&mask);// 位运算取模wheel[stopIndex].add(timeout);// O(1)追加returntimeout;}// Worker线程核心循环privatefinalclassWorkerimplementsRunnable{publicvoidrun(){do{longdeadline=waitForNextTick();// 等待到下一个tickintidx=(int)(tick&mask);HashedWheelBucketbucket=wheel[idx];bucket.expireTimeouts(deadline);// 执行到期任务tick++;// 指针前进}while(!shutdown);}}}

四、实战:实现延迟消息

publicclassDelayQueueExample{privatestaticfinalHashedWheelTimertimer=newHashedWheelTimer();publicstaticvoidsendDelayed(Stringmsg,longdelayMs){timer.newTimeout(timeout->{System.out.println("延迟消息到达: "+msg);},delayMs,TimeUnit.MILLISECONDS);}publicstaticvoidmain(String[]args)throwsException{sendDelayed("订单超时取消",5000);sendDelayed("支付超时提醒",10000);Thread.sleep(15000);timer.stop();}}

五、优缺点与适用场景

特性说明
✅ 插入O(1)计算槽位→追加到链表
✅ 内存小只需环形数组+链表
❌ 精度有限受tickDuration影响
❌ 不适合低延迟毫秒级精度够用

适用:心跳检测、超时重试、延迟消息、连接空闲检测


上一篇【第34篇】Netty Selector优化——为什么比JDK NIO快这么多
下一篇【第36篇】Netty时间轮高级应用——10亿级定时任务的工程实践