为什么会出现Selector
在阻塞模式下,相关方法(如ServerSocketChannel.accept
和SocketChannel.read
)会导致线程暂停。当没有连接建立或没有数据可读时,线程会处于等待状态,尽管此时线程不占用CPU资源,但线程本身处于闲置状态。
阻塞模式的缺点
- 线程暂停:在没有连接或数据可读时,线程会被暂停。
- 资源闲置:线程在等待期间不执行任何有用工作,导致资源浪费。
非阻塞模式
使用Java NIO,我们可以将通道(Channel)设置为非阻塞模式。在这种模式下,即使在没有连接建立或没有数据可读时,相关方法也会立即返回,而不是让线程暂停。
非阻塞模式的优点
- 线程不会暂停:在没有连接或数据可读时,线程可以继续执行其他任务。
- 提高资源利用率:线程在等待期间可以执行其他有用工作。
非阻塞模式的缺点
- CPU占用:由于线程需要不断轮询通道状态,这可能导致CPU占用率过高。
- 数据复制时的阻塞:尽管线程在等待数据写入Channel时不会被阻塞,但在数据实际从内核空间复制到用户空间时,线程仍然是阻塞的。
Selector 和 Channel 关系
Selector(选择器)是Java NIO中的一个核心组件,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写或已连接。通过Selector,我们可以实现单线程管理多个channels,即管理多个网络连接。
使用Selector的好处
- 减少线程数量:使用更少的线程来处理多个channels,减少了线程上下文切换的开销。
- 提高响应速度:当某个channel有事件发生时,Selector能够立即感知并处理。
- 高效利用资源:线程在等待期间可以处理其他事件,提高了资源的利用率。
注意事项
- 多路复用仅适用于网络IO:普通文件IO无法利用多路复用。
- 避免无效轮询:使用Selector时,需要确保只有当有事件发生时才去处理,避免无效的轮询操作。
示例Demo
代码语言:java复制// 1. 创建Selector
Selector selector = Selector.open();
// 2. 配置ServerSocketChannel为非阻塞模式,并注册到Selector
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(8080));
ssc.register(selector, SelectionKey.OP_ACCEPT);
// 3. 处理事件循环
while (true) {
// 等待事件发生
int readyChannels = selector.select();
if (readyChannels == 0) continue;
// 遍历所有就绪的Channel
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// 处理新连接
} else if (key.isReadable()) {
// 读取数据
}
// ... 其他事件处理
// 移除已处理的Key,避免重复处理
keyIterator.remove();
}
}