前言

在不久前,我学习了服务端启动Netty时大概做了哪些事,但在做那些事情之前,我们还有一个重要的组件要构造+启动,它就是NioEventLoop,本章就来学下NioEventLoop,顺便做下笔记。

本章分为以下三个小节(加上本节算四个):

  • new ThreadPerTaskExecutor() - 创建NioEventLoop执行任务线程的线程执行器。
  • newChild() - 创建NioEventLoop对象数组,并配置一些核心参数。
  • chooserFactory.newChooser() - 线程选择器,负责给新连接绑定一个NioEventLoop

Netty Version: 4.1.6


先追下构造函数

一般在使用Netty时,我们都会看到如下两行代码:

        // 负责接收新连接并抛给workerGroup
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 负责处理新连接
        EventLoopGroup workerGroup = new NioEventLoopGroup();

在这里我就不贴完整代码了,因为根本用不上~

先进入new NioEventLoopGroup();看到如下代码:

    public NioEventLoopGroup() {
        this(0);
    }
  • 其中0就是默认设置的线程数,后面会变。

继续,进入this:

    public NioEventLoopGroup(int nThreads) {
        this(nThreads, (Executor) null);
    }
  • 传了一个null引用的Executor,另外这里补充一个知识点:null可以被强制类型转换成任意类型的对象

继续,进入this:

    public NioEventLoopGroup(
            int nThreads, Executor executor, final SelectorProvider selectorProvider) {
        this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
    }

继续,进入this:

    public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
                             final SelectStrategyFactory selectStrategyFactory) {
        super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
    }

继续,进入super【路标1】:

    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

好了,俄罗斯套娃先暂停一下,顺便留个路标,因为这里有个有意思的参数:DEFAULT_EVENT_LOOP_THREADS,如果指定的线程数是0,则会将线程数替换成这个值。

那么这个值到底是多少呢?我们不妨跟进去看一下:

DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));

关于Runtime.getRuntime().availableProcessors()我在jdk8文档得到的解释是:返回cpu的核心数。

但我自己测试时,却发现有些不妥,我的cpu明明是四核,但是却返回8,运行结果如下图:

availableProcessors结果.png

然后我在网上稍微查了下资料,然后再看下任务管理器的cpu参数,终于知道怎么回事了:

任务管理器.png

结果:我的cpu是4核8线程(逻辑处理器),而Runtime.getRuntime().availableProcessors()返回的是逻辑处理器的个数。

所以,如果我不指定线程数,Netty默认会设置2*逻辑处理器个数的线程数,在我的电脑上就是16个线程。

虽然我没有过线程数调优的经验,但是上面讲到Runtime.getRuntime().availableProcessors()的"坑"也还是要注意的,自己电脑跑没啥问题,可万一是线上环境可能就需要注意了。

同时,线程数设置为:2*逻辑处理器个数的线程数 也算是比较常见的参考数值了,之前在mysql的文档中也见过。


好了,中途追查了一下availableProcessors()的"bad case"后,我们重新回到俄罗斯套娃中,也就是上面的【路标1】,为了我这种方便懒人,下面再贴下坐标:
io.netty.channel.MultithreadEventLoopGroup#MultithreadEventLoopGroup(int, java.util.concurrent.Executor, java.lang.Object...)

    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

继续,进入super:

    protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
        this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
    }

继续,进入this,我们就能看到本章的三个主角了:
io.netty.util.concurrent.MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, Executor, EventExecutorChooserFactory, Object...)

MultithreadEventExecutorGroup构造函数.png

上面这个构造函数可以说是所有小节的起点了,如果在后面的小节中忘记是如何进到这个构造函数的,也可以翻阅此篇。

由于我不想将一篇博客篇幅拉太长,关于new ThreadPerTaskExecutor、newChild、chooserFactory.newChooser我就放到后面一小节记录一个。


小结

  • Netty在设置线程数时,默认设置的是:逻辑处理器*2
  • 构造函数的主要逻辑:创建NioEventLoop线程的线程执行器、创建NioEventLoop对象数组、创建线程选择器。