技术杂谈

Java NIO Selector 多路复用选择器

2019-02-16  本文已影响2人  香芋牛奶面包

引言

上篇文章我们简单的使用了NIOChannel通道,本期我们主要来介绍一下选择器(Selector
)的使用,SelectorJava NIO核心组件中的一个。在之前介绍5种I/O模型的时候,有介绍过多路复用模型。多路复用模型使得我们可以使用一个线程来管理成千上万的连接,避免了线程上下文切换带来的开销,使得性能得到极大的提升

基本模型

选择器Selector使用的基本模式,跟传统BIO处理模型不一样。传统BIO往往我们会使用多线程来提升处理性能,也就是说每接入一个Client,Server端就会为其新开一个线程,以此来提升并发吞吐量,使用这种模式的弊端很明显,因为线程是系统非常重要的资源,当并发量少的时候,感觉不到,一旦并发量上来,就会出现瓶颈。

再来看Selector是如何处理的,首先每接入一个Client,我们可以通过Selector选择器注册感兴趣的事件,然后通过一个线程去不停的轮询检测各个Client是否有感兴趣的事件发生,有则顺序处理该Client就绪的各个事件。

image.png

Selector 使用实例

我们先来看一个Selector非常常见的使用例子

try {
      // 打开一个通道
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.socket().bind(new InetSocketAddress(8890));
    // 打开 选择器 selector
    Selector selector = Selector.open();
    // 设置非阻塞
    serverSocketChannel.configureBlocking(false);
    // 为ServerSocketChannel注册 OP_ACCEPT 事件,返回一个SelectionKey 对象
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    while (true) {
            // 返回至少有一个事件就绪的通道数,该方法是阻塞的
        int readyNum = selector.select();
        if (readyNum == 0) {
            continue;
        }
            
            // 返回就绪的 SelectionKey集合
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        Iterator<SelectionKey> iteratorKeys = selectionKeys.iterator();
        
        // 遍历所有就绪的SelectionKey集合
        while (iteratorKeys.hasNext()) {
            SelectionKey key = iteratorKeys.next();
            iteratorKeys.remove();
                 // 判断就绪的具体事件
            if (key.isValid()) {
                if (key.isAcceptable()) {
                    ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                    // 接受一个新连接
                    SocketChannel channel = serverChannel.accept();
                    channel.configureBlocking(false);
                    // 为该Channel注册可读事件
                    channel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    ...
                }
            }
        }
    }
} catch (IOException e) {
    e.printStackTrace();
}

通过查看上面的代码,可知我们通过Selector.open()创建了一个选择器,并且通过SocketChannelregister方法注册了选择器和事件,其中register方法会返回一个SelectionKey对象,该对象其实就是维护了ChannelSelector的对应关系

SelectionKey

上文我们提到SelectionKey对象维护了ChannelSelector的对应关系,现在我们来看下
SelectionKey对象内部几个非常重要的属性和方法

Selector

讲完SelectionKeySelector的关系之后,我们再次回到Selector类,我们首先需要知道Selector中3个重要的SelectionKey集合

接下来将会介绍上文例子中所用到的几个方法

Selector.open()

这个静态方法可以打开一个Selector选择器

public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();
}

通过查看源码可知在Linux系统下默认会使用EpollSelectorImpl来作为Selector实现类,注意各个系统下默认的实现类是不同的。

selector.select()

Selectorselect()方法会返回事件就绪的SelectionKey数目(也就是就绪的Channel数)

并且该方法会一直阻塞直到至少一个channel被选择(即,该channel注册的事件发生了)为止,除非当前线程发生中断或者selectorwakeup方法被调用

该方法还有一个重载select(long timeout),可以自定义超时时间

selector.selectNow()

该方法与上面方法类似,但该方法不会发生阻塞,即使没有一个channel被选择也会立即返回

selector.selectedKeys()

返回已就绪的SelectionKey集合,该方法在执行了selector.select()后调用,因为在执行selector.select()后就表示至少有一个SelectionKey已经就绪

尾言

好了,本篇文章就介绍到这里了,篇幅有限加上本人对NIO的理解也有待加深,希望可以在之后更深入的对Java NIO的实现进行分析。

博客原文地址戳这里

上一篇 下一篇

猜你喜欢

热点阅读