接下来,我们来分析 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 的源码,可以看到,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, Object...args) {
this(nThreads, threadFactory == null ? null : new ThreadPerTaskExecutor(threadFactory), args);
}
到这一步的时候,new ThreadPerTaskExecutor(threadFactory) 会构造一个 executor。
我们现在还不知道这个
executor怎么用。这里我们先看下它的源码:
Executor作为线程池的最顶层接口, 我们知道,它只有一个execute(runnable)方法,从上面我们可以看到,实现类ThreadPerTaskExecutor的逻辑就是每来一个任务,新建一个线程。我们先记住这个,前面也说了,它是给
NioEventLoop用的,不是给NioEventLoopGroup用的。
public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
this.threadFactory = threadFactory;
}
@Override
public void execute(Runnable command) {
// 为每个任务新建一个线程
threadFactory.newThread(command).start();
}
}
上一步设置完了 executor,我们继续往下看:
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object...args) {
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
这一步设置了 chooserFactory,用来实现从线程池中选择一个线程的选择策略。
ChooserFactory的逻辑比较简单,我们看下DefaultEventExecutorChooserFactory的实现:这里设置的策略也很简单:
1、如果线程池的线程数量是
2^n,采用下面的方式会高效一些:2、如果不是,用取模的方式:
@Override
public EventExecutorChooser newChooser

本文主要分析Netty中的线程池,从NioEventLoopGroup的创建入手,介绍其构造方法及参数,指出无参构造时线程数默认设为CPU核心数两倍。还阐述了NioEventLoop的工作流程,包括线程启动、任务执行及IO操作时间比重控制等内容。
最低0.47元/天 解锁文章
2671

被折叠的 条评论
为什么被折叠?



