今天是猿灯塔“365篇原创计划”第五篇。 接下来的时间灯塔君持续更新Netty系列一共九篇
Netty 源码解析(一): 开始
Netty 源码解析(二): Netty 的 Channel
Netty 源码解析(三): Netty的 Future 和 Promise
Netty 源码解析(四): Netty 的 ChannelPipeline
当前:Netty 源码解析(五): Netty 的线程池分析
Netty 源码解析(六): Channel 的 register 操作
Netty 源码解析(七): NioEventLoop 工作流程
Netty 源码解析(八): 回到 Channel 的 register 操作
Netty 源码解析(九): connect 过程和 bind 过程分析
今天呢!灯塔君跟大家讲:
Netty 的线程池分析
Netty 中的线程池 EventLoopGroup
接下来,我们来分析 Netty 中的线程池。Netty 中的线程池比较不好理解,因为它的类比较多,而且它们之间的关系错综复杂。看下图,感受下 NioEventLoop 类和 NioEventLoopGroup 类的继承结构:
这张图我按照继承关系整理而来,大家仔细看一下就会发现,涉及到的类确实挺多的。本节来给大家理理清楚这部分内容。
首先,我们说的 Netty 的线程池,指的就是 NioEventLoopGroup 的实例;线程池中的单个线程,指的是右边 NioEventLoop 的实例。
回顾下我们第一节介绍的 Echo 例子,客户端和服务端的启动代码中,最开始我们总是先实例化 NioEventLoopGroup:
// EchoClient 代码最开始:
EventLoopGroup group = new NioEventLoopGroup();
// EchoServer 代码最开始:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
下面,我们就从 NioEventLoopGroup 的源码开始进行分析。
我们打开 NioEventLoopGroup 的源码,可以看到,NioEventLoopGroup 有多个构造方法用于参数设置,最简单地,我们采用无参构造函数,或仅仅设置线程数量就可以了,其他的参数采用默认值。
比如上面的代码中,我们只在实例化 bossGroup 的时候指定了参数,代表该线程池需要一个线程。
public NioEventLoopGroup() {
this(0);
}
public NioEventLoopGroup(int nThreads) {
this(nThreads, (Executor) null);
}
...
// 参数最全的构造方法
public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory,
final RejectedExecutionHandler rejectedExecutionHandler) {
// 调用父类的构造方法
super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory, rejectedExecutionHandler);
}
我们来稍微看一下构造方法中的各个参数:
- nThreads:这个最简单,就是线程池中的线程数,也就是 NioEventLoop 的实例数量。
- executor:我们知道,我们本身就是要构造一个线程池(Executor),为什么这里传一个 executor 实例呢?它其实不是给线程池用的,而是给 NioEventLoop 用的,以后再说。
- chooserFactory:当我们提交一个任务到线程池的时候,线程池需要选择(choose)其中的一个线程来执行这个任务,这个就是用来实现选择策略的。
- selectorProvider:这个简单,我们需要通过它来实例化 JDK 的 Selector,可以看到每个线程池都持有一个 selectorProvider 实例。
- selectStrategyFactory:这个涉及到的是线程池中线程的工作流程,在介绍 NioEventLoop 的时候会说。
- rejectedExecutionHandler:这个也是线程池的好朋友了,用于处理线程池中没有可用的线程来执行任务的情况。在 Netty 中稍微有一点点不一样,这个是给 NioEventLoop 实例用的,以后我们再详细介绍。
这里介绍这些参数是希望大家有个印象而已,大家发现没有,在构造 NioEventLoopGroup 实例时的好几个参数,都是用来构造 NioEventLoop 用的。
下面,我们从 NioEventLoopGroup 的无参构造方法开始,跟着源码走:
public NioEventLoopGroup() {
this(0);
}
然后一步步走下去,到这个构造方法:
public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, threadFactory, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
大家自己要去跟一下源码,这样才知道中间设置了哪些默认值,下面这几个参数都被设置了默认值:
-
selectorProvider = SelectorProvider.provider()
这个没什么好说的,调用了 JDK 提供的方法
-
selectStrategyFactory = DefaultSelectStrategyFactory.INSTANCE
这个涉及到的是线程在做 select 操作和执行任务过程中的策略选择问题,在介绍 NioEventLoop 的时候会用到。
-
rejectedExecutionHandler = RejectedExecutionHandlers.reject()
大家进去看一下 reject() 方法,也就是说,Netty 选择的默认拒绝策略是:抛出异常
跟着源码走,我们会来到父类 MultithreadEventLoopGroup 的构造方法中:
protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
}
这里我们发现,如果采用无参构造函数,那么到这里的时候,默认地 nThreads 会被设置为 *CPU 核心数 2。大家可以看下 DEFAULT_EVENT_LOOP_THREADS 的默认值,以及 static 代码块的设值逻辑。
我们继续往下走:
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory