Netty源码分析---waken方法详解
本文深入解析了Netty中NioEventLoop的两个关键机制:SelectStrategy状态和wakenUp标志。首先指出SelectStrategy.CONTINUE和BUSY_WAIT在实际运行中极少出现,默认实现仅返回SELECT状态或selectNow()结果,这两个特殊状态主要是为扩展性预留。其次详细阐述了wakenUp原子标志的作用,通过时序图展示了它在避免"唤醒丢失&
相关文章链接
processSelectedKeys() vs runAllTasks()
NioServerSocketChannel-Unsafe初始化详解
问题一:SelectStrategy.CONTINUE 和 BUSY_WAIT
1. 这两种状态确实很少见
是的!在 NioEventLoop 中,这两个状态基本不会出现。
让我看看实际的代码:
// DefaultSelectStrategy 的实现
public final class DefaultSelectStrategy implements SelectStrategy {
@Override
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
// 只会返回两种情况:
// 1. 如果有任务:返回 selectSupplier.get()(即 selectNow() 的返回值)
// 2. 如果没任务:返回 SelectStrategy.SELECT(-1)
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
}
所以实际上只会返回:
SelectStrategy.SELECT(-1):没有任务时>= 0的数字:有任务时,selectNow() 的返回值
2. selectNow() 不会返回 CONTINUE 或 BUSY_WAIT
int selectNow() throws IOException {
return selector.selectNow(); // JDK 的方法
}
selectNow() 的返回值:
>= 0:表示有多少个 Channel 准备好了- 永远不会返回负数
- 永远不会返回 -2 或 -3
JDK 文档说明:
// java.nio.channels.Selector.selectNow()
// Returns: The number of keys, possibly zero
// 返回值:准备好的 key 的数量,可能是 0
3. CONTINUE 和 BUSY_WAIT 是为了扩展性
这两个常量是为了让 Netty 的设计更灵活,允许自定义的 SelectStrategy 实现返回这些值。
CONTINUE 的用途:
case SelectStrategy.CONTINUE:
continue; // 跳过本次循环,直接开始下一次
使用场景(理论上):
// 自定义的 SelectStrategy
public class MySelectStrategy implements SelectStrategy {
@Override
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) {
if (某些特殊条件) {
return SelectStrategy.CONTINUE; // 跳过本次循环
}
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
}
BUSY_WAIT 的用途:
case SelectStrategy.BUSY_WAIT:
// fall-through to SELECT since the busy-wait is not supported with NIO
为什么 NIO 不支持?
- Busy-wait 是指不断轮询,不阻塞
- 这会消耗大量 CPU
- NIO 的设计理念是事件驱动,不需要 busy-wait
- 所以即使返回 BUSY_WAIT,也会 fall-through 到 SELECT
4. 总结
实际使用中:
├─ 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 是什么?
private final AtomicBoolean wakenUp = new AtomicBoolean();
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 = false
boolean oldWakenUp = wakenUp.getAndSet(false);
// 现在 wakenUp = false
// 步骤 2:调用 select(可能阻塞)
select(oldWakenUp);
selector.select(timeoutMillis);
// 步骤 3:select 返回后,检查 wakenUp
if (wakenUp.get()) {
// 如果 wakenUp = true,说明有人想唤醒
selector.wakeup(); // 再唤醒一次,确保安全
}
// ===== 外部线程 =====
// 步骤 A:提交任务
execute(task);
addTask(task);
// 步骤 B:尝试唤醒
wakeup(false);
// 尝试将 wakenUp 从 false 改为 true
if (wakenUp.compareAndSet(false, true)) {
// 成功改为 true,调用 wakeup
selector.wakeup();
}
详细的时间线分析
场景 1:wakeup() 在 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() // 再唤醒一次
结果:✓ 正常工作
场景 2:wakeup() 在 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() // 再唤醒一次(这次可能不需要,但为了安全)
结果:✓ 正常工作
场景 3:wakeup() 在 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() // 再唤醒一次
↓
下次循环会重新设置为 false
5. 用代码示例说明
// 示例:完整的交互过程
// ===== 循环开始 =====
// EventLoop 线程
wakenUp.getAndSet(false); // wakenUp = false
System.out.println("准备 select,wakenUp = " + wakenUp.get()); // false
selector.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); // 重置为 false
select(); // 可能阻塞
if (wakenUp.get()) { // 检查是否有人想唤醒
selector.wakeup(); // 再唤醒一次,确保安全
}
// 外部线程
if (wakenUp.compareAndSet(false, true)) { // 尝试改为 true
selector.wakeup(); // 唤醒 selector
}
为什么要"再唤醒一次"?
因为 selector.wakeup() 的效果只对"下一次" select() 有效。如果在 select() 返回后又有新任务提交,需要确保下一次 select() 也能被唤醒。
希望这次解释清楚了!wakenUp 是一个精妙的设计,用于解决并发场景下的唤醒问题。
我来详细解释一下 wakenUp.compareAndSet(false, true) 这个方法的作用和原理。
更多推荐


所有评论(0)