NIO Selector 详解

127 阅读2分钟

Java NIO(New I/O)中的 Selector 是一个关键组件,用于实现非阻塞 I/O 操作。它允许一个线程同时监视多个通道(Channel)上的事件,比如连接请求、数据到达等。通过使用 Selector,可以提高应用程序的性能和资源利用率,尤其是在处理大量并发连接时。

Selector 的基本概念

  • 非阻塞 I/O:Java NIO 支持非阻塞模式,允许通道在没有可用数据时立即返回,而不是阻塞线程。这使得一个线程可以管理多个连接。

  • 通道(Channel):NIO 的核心概念之一,代表一个能够执行 I/O 操作的对象,如 SocketChannelServerSocketChannel 等。

  • 选择器(Selector):用于监视多个通道的 I/O事件。通过 Selector,一个线程可以高效地管理多个通道。

  • 选择键(SelectionKey):表示注册在 Selector 上的通道及其感兴趣的事件。每个通道在 Selector 上注册后,会生成一个 SelectionKey

Selector 的主要方法

  1. open()

    Selector selector = Selector.open();
    

    创建一个新的 Selector 实例。

  2. register()

    SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
    

    将通道注册到 Selector 上,并指定感兴趣的事件(如 OP_READOP_WRITEOP_CONNECTOP_ACCEPT)。

  3. select()

    int readyChannels = selector.select();
    

    阻塞直到至少有一个通道准备好进行 I/O 操作。

  4. selectNow()

    int readyChannels = selector.selectNow();
    

    非阻塞版本的 select(),立即返回可用的通道数。

  5. select(long timeout)

    int readyChannels = selector.select(1000);
    

    带超时的 select() 方法,阻塞到有通道准备好或超时。

  6. selectedKeys()

    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    

    返回一组 SelectionKey,代表已准备好的通道。

  7. wakeup()

    selector.wakeup();
    

    使阻塞在 select() 方法上的线程立即返回。

  8. close()

    selector.close();
    

    关闭 Selector,释放资源。

使用 Selector 的基本步骤

  1. 打开 Selector

    Selector selector = Selector.open();
    
  2. 打开并配置通道

    ServerSocketChannel serverChannel = ServerSocketChannel.open();
    serverChannel.bind(new InetSocketAddress(port));
    serverChannel.configureBlocking(false);
    
  3. 将通道注册到 Selector

    serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    
  4. 循环等待事件

    while (true) {
        int readyChannels = selector.select();
        if (readyChannels == 0) continue;
    
        Set<SelectionKey> selectedKeys = selector.selectedKeys();
        Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
    
        while (keyIterator.hasNext()) {
            SelectionKey key = keyIterator.next();
    
            if (key.isAcceptable()) {
                // 处理接受事件
            } else if (key.isReadable()) {
                // 处理读取事件
            } else if (key.isWritable()) {
                // 处理写入事件
            } else if (key.isConnectable()) {
                // 处理连接事件
            }
    
            keyIterator.remove();
        }
    }
    

注意事项

  • 线程安全性Selector 不是线程安全的,通常一个 Selector 由一个线程管理。
  • 资源管理:确保在不使用时关闭通道和选择器,以释放资源。
  • 事件处理:处理完每个 SelectionKey 后,需要从 selectedKeys 集合中移除,以避免重复处理。

使用 Selector 可以有效地管理多个网络连接,尤其适用于需要处理大量并发连接的服务器应用程序。

参考:

NIO SelectionKey 详解
我们在看 xxljob 源码时 ,就有哟过netty 做http服务器的代码