上一篇【第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亿级定时任务的工程实践