调用 selector.wakeup() 可以立即中断当前阻塞在 select() 方法上的线程,使其返回并继续执行后续逻辑。这通常用于从另一个线程向正在等待的 selector 发送信号,实现线程间的异步通信或强制唤醒。

如何从其他线程安全地唤醒 Selector
通过持有 Selector 对象的引用,可以在任何地方调用其 wakeup() 方法。当被阻塞的线程收到唤醒信号后,select() 会立即返回,即使此时没有任何就绪的 Channel。这种机制常用于实现优雅关闭或定时任务触发。

import java.nio.channels.Selector;
import java.io.IOException;
public class WakeupExample {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
// 模拟一个后台线程负责唤醒
Thread waiter = new Thread(() -> {
try {
System.out.println("开始阻塞等待...");
int readyChannels = selector.select(); // 这里会阻塞
System.out.println("被唤醒!就绪通道数: " + readyChannels);
} catch (IOException e) {
e.printStackTrace();
}
});
waiter.start();
// 主线程休眠后唤醒
Thread.sleep(2000);
System.out.println("主线程调用 wakeup()");
selector.wakeup(); // 关键操作
waiter.join();
}
}
wakeup() 对 select() 返回值的影响
当 wakeup() 被调用时,select() 方法不会抛出异常,而是直接返回一个非负整数。这个返回值表示当前就绪的 Channel 数量,但在大多数情况下,由于唤醒是即时触发的,可能并没有新的 I/O 事件发生,因此返回值可能是 0。开发者需要在代码中处理这种情况,区分是因为有数据可读而返回,还是因为被唤醒而返回。

import java.nio.channels.Selector;
import java.util.Set;
import java.util.Iterator;
import java.io.IOException;
public class SelectReturnCheck {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
Thread t = new Thread(() -> {
try {
int n = selector.select();
System.out.println("Select returned: " + n);
if (n > 0) {
Set keys = selector.selectedKeys();
Iterator it = keys.iterator();
while(it.hasNext()) {
System.out.println("Processing key...");
it.remove();
}
}
} catch (IOException e) {}
});
t.start();
Thread.sleep(1000);
selector.wakeup();
t.join();
}
}
基于重复调用 wakeup 的条件判断问题
如果在 select() 已经返回之后再次调用 wakeup(),下一次 select() 调用将立即返回,而不会阻塞。这是一个重要的状态特性:一旦唤醒,Selector 就处于“已唤醒”状态,直到下一次 select() 被调用。如果业务逻辑中没有正确处理这个状态,可能会导致循环中的 select() 变成空转,消耗 CPU 资源。因此,必须在每次 select() 返回后,检查是否有实际的处理逻辑,或者确保在循环开始前重置状态。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











