Netty源码分析---waken方法详解
相关文章链接位运算详解waken方法详解ThreadPerTaskExecutor与线程创建详解processSelectedKeys() vs runAllTasks()NioServerSocketChannel-Unsafe初始化详解NioEventLoop的run方法详解NioEventLoopGroup深度解析inEventLoop方法详解executionMask详解Netty源码分析–认真系列(一)Netty源码分析–认真系列(二)问题一SelectStrategy.CONTINUE 和 BUSY_WAIT1. 这两种状态确实很少见是的在 NioEventLoop 中这两个状态基本不会出现。让我看看实际的代码// DefaultSelectStrategy 的实现publicfinalclassDefaultSelectStrategyimplementsSelectStrategy{OverridepublicintcalculateStrategy(IntSupplierselectSupplier,booleanhasTasks)throwsException{// 只会返回两种情况// 1. 如果有任务返回 selectSupplier.get()即 selectNow() 的返回值// 2. 如果没任务返回 SelectStrategy.SELECT-1returnhasTasks?selectSupplier.get():SelectStrategy.SELECT;}}所以实际上只会返回SelectStrategy.SELECT-1没有任务时 0的数字有任务时selectNow() 的返回值2. selectNow() 不会返回 CONTINUE 或 BUSY_WAITintselectNow()throwsIOException{returnselector.selectNow();// JDK 的方法}selectNow() 的返回值 0表示有多少个 Channel 准备好了永远不会返回负数永远不会返回 -2 或 -3JDK 文档说明// java.nio.channels.Selector.selectNow()// Returns: The number of keys, possibly zero// 返回值准备好的 key 的数量可能是 03. CONTINUE 和 BUSY_WAIT 是为了扩展性这两个常量是为了让 Netty 的设计更灵活允许自定义的 SelectStrategy 实现返回这些值。CONTINUE 的用途caseSelectStrategy.CONTINUE:continue;// 跳过本次循环直接开始下一次使用场景理论上// 自定义的 SelectStrategypublicclassMySelectStrategyimplementsSelectStrategy{OverridepublicintcalculateStrategy(IntSupplierselectSupplier,booleanhasTasks){if(某些特殊条件){returnSelectStrategy.CONTINUE;// 跳过本次循环}returnhasTasks?selectSupplier.get():SelectStrategy.SELECT;}}BUSY_WAIT 的用途caseSelectStrategy.BUSY_WAIT:// fall-through to SELECT since the busy-wait is not supported with NIO为什么 NIO 不支持Busy-wait 是指不断轮询不阻塞这会消耗大量 CPUNIO 的设计理念是事件驱动不需要 busy-wait所以即使返回 BUSY_WAIT也会 fall-through 到 SELECT4. 总结实际使用中 ├─ SelectStrategy.SELECT (-1) ✓ 常见没任务时 ├─ 0 的数字 ✓ 常见有任务时selectNow() 返回 ├─ SelectStrategy.CONTINUE (-2) ✗ 几乎不用 └─ SelectStrategy.BUSY_WAIT (-3) ✗ 几乎不用 selectNow() 的返回值 ├─ 0 ✓ 没有 Channel 准备好 ├─ 1 ✓ 有 1 个 Channel 准备好 ├─ 2 ✓ 有 2 个 Channel 准备好 └─ ... ✓ 更多 selectNow() 不会返回 ✗ -1 (SELECT) ✗ -2 (CONTINUE) ✗ -3 (BUSY_WAIT)问题二wakenUp 的作用这是一个非常重要但容易混淆的机制。让我用最详细的方式解释。1. wakenUp 是什么privatefinalAtomicBooleanwakenUpnewAtomicBoolean();wakenUp 是一个原子布尔变量用于标记是否有人想要唤醒 EventLoop。2. 为什么需要 wakenUp让我用一个问题场景来说明问题场景竞态条件时间线 T1: EventLoop 线程准备调用 select() 准备阻塞... T2: 外部线程提交任务 execute(task) addTask(task) // 任务已加入队列 selector.wakeup() // 想要唤醒 EventLoop T3: EventLoop 线程调用 select() selector.select() // 开始阻塞 问题 如果 T2 的 wakeup() 在 T3 的 select() 之前调用 那么 select() 可能不会被唤醒 因为 wakeup() 的效果只对下一次 select() 有效用图解释情况 A正常情况 EventLoop 线程 外部线程 | | |--select() | | 阻塞中... | | |--execute(task) | | | |--wakeup() | | |--被唤醒 | | | ✓ 正常工作 情况 B问题情况 EventLoop 线程 外部线程 | | |--准备 select() | | |--execute(task) | | | |--wakeup() | | (但 select 还没开始) | | |--select() | | 阻塞中... | | (wakeup 的效果已经用掉了) | | ✗ 无法被唤醒任务得不到处理3. wakenUp 如何解决这个问题完整的流程// EventLoop 线程 // 步骤 1准备 select 之前设置 wakenUp falsebooleanoldWakenUpwakenUp.getAndSet(false);// 现在 wakenUp false// 步骤 2调用 select可能阻塞select(oldWakenUp);selector.select(timeoutMillis);// 步骤 3select 返回后检查 wakenUpif(wakenUp.get()){// 如果 wakenUp true说明有人想唤醒selector.wakeup();// 再唤醒一次确保安全}// 外部线程 // 步骤 A提交任务execute(task);addTask(task);// 步骤 B尝试唤醒wakeup(false);// 尝试将 wakenUp 从 false 改为 trueif(wakenUp.compareAndSet(false,true)){// 成功改为 true调用 wakeupselector.wakeup();}详细的时间线分析场景 1wakeup() 在 select() 之前T1: EventLoop 线程 wakenUp.getAndSet(false) // wakenUp false T2: 外部线程 execute(task) addTask(task) wakenUp.compareAndSet(false, true) // wakenUp true ✓ selector.wakeup() // 调用 wakeup T3: EventLoop 线程 selector.select() // 立即返回因为刚才 wakeup 了 T4: EventLoop 线程 if (wakenUp.get()) // wakenUp true selector.wakeup() // 再唤醒一次 结果✓ 正常工作场景 2wakeup() 在 select() 之后T1: EventLoop 线程 wakenUp.getAndSet(false) // wakenUp false T2: EventLoop 线程 selector.select() // 开始阻塞 T3: 外部线程 execute(task) addTask(task) wakenUp.compareAndSet(false, true) // wakenUp true ✓ selector.wakeup() // 唤醒 selector T4: EventLoop 线程 selector.select() 返回被唤醒 T5: EventLoop 线程 if (wakenUp.get()) // wakenUp true selector.wakeup() // 再唤醒一次这次可能不需要但为了安全 结果✓ 正常工作场景 3wakeup() 在 select() 和检查之间T1: EventLoop 线程 wakenUp.getAndSet(false) // wakenUp false T2: EventLoop 线程 selector.select() // 开始阻塞 T3: 外部线程 execute(task) addTask(task) wakenUp.compareAndSet(false, true) // wakenUp true ✓ selector.wakeup() // 唤醒 selector T4: EventLoop 线程 selector.select() 返回 T5: 外部线程又提交了一个任务 execute(task2) addTask(task2) wakenUp.compareAndSet(false, true) // 失败wakenUp 已经是 true // 不调用 selector.wakeup() T6: EventLoop 线程 if (wakenUp.get()) // wakenUp true selector.wakeup() // 再唤醒一次确保 task2 也能被处理 结果✓ 正常工作通过再次 wakeup 确保安全4. wakenUp 的状态转换初始状态wakenUp false EventLoop 准备 select wakenUp.getAndSet(false) // 确保是 false ↓ wakenUp false 外部线程提交任务 wakenUp.compareAndSet(false, true) // 尝试改为 true ↓ 如果成功wakenUp true调用 selector.wakeup() 如果失败wakenUp 已经是 true不需要再 wakeup EventLoop select 返回 if (wakenUp.get()) // 检查是否为 true selector.wakeup() // 再唤醒一次 ↓ 下次循环会重新设置为 false5. 用代码示例说明// 示例完整的交互过程// 循环开始 // EventLoop 线程wakenUp.getAndSet(false);// wakenUp falseSystem.out.println(准备 selectwakenUp wakenUp.get());// falseselector.select();// 阻塞// 此时外部线程提交任务 // 外部线程execute(()-System.out.println(任务));addTask(task);System.out.println(任务已加入队列);if(wakenUp.compareAndSet(false,true)){System.out.println(wakenUp 改为 true调用 wakeup);selector.wakeup();}else{System.out.println(wakenUp 已经是 true不需要 wakeup);}// EventLoop 被唤醒 // EventLoop 线程System.out.println(select 返回);if(wakenUp.get()){System.out.println(wakenUp true再唤醒一次);selector.wakeup();}processSelectedKeys();runAllTasks();// 处理任务// 循环结束回到开始 6. 总结wakenUp 的作用标记作用标记是否有人想要唤醒 EventLoop防止丢失唤醒确保在任何时机提交任务都能唤醒 EventLoop避免重复唤醒如果已经有人唤醒了就不需要再唤醒关键点// EventLoop 线程wakenUp.getAndSet(false);// 重置为 falseselect();// 可能阻塞if(wakenUp.get()){// 检查是否有人想唤醒selector.wakeup();// 再唤醒一次确保安全}// 外部线程if(wakenUp.compareAndSet(false,true)){// 尝试改为 trueselector.wakeup();// 唤醒 selector}为什么要再唤醒一次因为selector.wakeup()的效果只对下一次select()有效。如果在select()返回后又有新任务提交需要确保下一次select()也能被唤醒。希望这次解释清楚了wakenUp 是一个精妙的设计用于解决并发场景下的唤醒问题。我来详细解释一下wakenUp.compareAndSet(false, true)这个方法的作用和原理。