Selector的wakeup()方法专用于中断select()或select(long)的阻塞等待,使线程立即返回并重新轮询;它线程安全、幂等,仅对select阻塞有效,不作用于wait()、sleep()等其他阻塞机制。

Selector 的 wakeup() 方法不是用来唤醒“阻塞变量”或普通线程的,它专为中断 Selector.select() 及其变体(select(long))的阻塞等待而设计。所谓“阻塞变量处理线程”,如果是指正在调用 select() 的 I/O 事件循环线程,那么 wakeup() 正是它的标准唤醒手段;如果是指其他任意阻塞在 Object.wait()、Thread.sleep() 或自定义锁上的线程,则 wakeup() 完全无效,也不应被误用。
明确适用场景:只对 select 阻塞有效
wakeup() 的作用对象非常具体:
- 仅当线程正执行
selector.select()、selector.select(timeout)且尚未返回时,调用wakeup()才会立即中断该阻塞,使方法返回(可能返回 0) - 若线程阻塞在
Object.wait()、LockSupport.park()、BlockingQueue.take()等任何其他机制上,wakeup()不产生任何效果 - 它不唤醒线程本身,而是唤醒“一次 select 调用”——线程继续运行,但需由你代码决定后续逻辑(如检查退出标志、执行定时任务等)
典型应用模式:注册变更后及时唤醒
多线程协作中最常见的唤醒需求是:工作线程向 Selector 注册新 Channel(例如完成连接建立、重启用写事件),而主线程正阻塞在 select() 中。此时必须唤醒,否则新注册不会被感知。
- 在注册操作完成后,**立刻**调用
selector.wakeup(),无需判断是否阻塞(它是幂等的) - 示例:
channel.register(selector, SelectionKey.OP_READ); selector.wakeup(); - 避免在
select()循环内部、尚未遍历完selectedKeys()时调用,以防干扰当前轮次处理
配合唤醒的必要清理动作
wakeup() 不会清空就绪键集合,也不会重置内部状态。若忽略清理,会导致重复处理或逻辑错乱:
- 每次
select()返回后,必须用Iterator遍历selectedKeys(),并对每个已处理的 key 调用it.remove() - 禁止使用 for-each 直接遍历
selectedKeys(),否则抛ConcurrentModificationException - 若唤醒后未及时处理 keys,下一轮
select()可能立即返回 0(空轮询),但旧 keys 仍在集合中,造成重复 dispatch
底层原理简述:不是信号,是内核事件注入
在 Linux 上,wakeup() 实质是向 Selector 内部维护的一个 eventfd(或 pipe)写入一个字节,该 fd 已被加入 epoll 实例。这触发 epoll_wait() 立即返回(通常返回就绪数 ≥ 0),而非因信号中断(EINTR)。JVM 随后会读取并消费这个字节,避免持续空轮询。
- Windows 下则通过本地 TCP 环回连接模拟类似行为(因 Winsock select 不支持 pipe)
- 这意味着唤醒是轻量、可控、无上下文切换开销的“软中断”,比基于信号的唤醒更可靠
- 它与网络 I/O 事件完全正交,仅用于用户态控制流干预

















