先贴出一张 NioEventLoopGroup
和 NioEventLoop
继承类图。
前言
示例代码
// BossGroup 仅需 1 线程处理连接请求
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// WorkerGroup 按 CPU 核心数动态分配(默认核心数的 2 倍)
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new MyChannelInitializer());
上面的代码使用 Netty 的主从 Reactor 架构。该架构由主 Reactor 和从 Reactor 组成:
- BossGroup(主 Reactor):负责监听 TCP 连接请求(
accept
事件)。通常配置为单线程(new NioEventLoopGroup(1)
)。 - WorkerGroup(从 Reactor):监听并处理已建立连接的 I/O 读写事件(read/write 事件)。Netty 默认将 WorkerGroup 中的线程数其设置为 CPU 核心数的 2 倍。
NioEventLoopGroup
是 Netty 中基于 Java NIO 实现的事件循环线程池,属于 Reactor 线程模型的核心组件。它可以看做一个线程池,管理一组 NioEventLoop
线程。每个 NioEventLoop
绑定一个独立线程和 Selector,以串行无锁化方式处理网络事件,确保线程安全。
我们可以将 NioEventLoop
看成是 NioEventLoopGroup
的一个特殊情况。即只管理一个 NioEventLoop
的 EventLoopGroup
。这也解释了为什么 EventLoop
接口继承了 EventLoopGroup
接口的原因。
从构造函数开始
NioEventLoopGroup
有多达 12 个构造函数。我们主要关注下面两个:
NioEventLoopGroup()
:无参构造。NioEventLoopGroup(int nThreads)
:单形参构造函数。nThreads
指定了线程池的线程数量。
public NioEventLoopGroup() {
this(0);
}
public NioEventLoopGroup(int nThreads) {
this(nThreads, (Executor) null);
}
无参构造函数以参数 nThreads = 0
调用单形参构造函数。
public NioEventLoopGroup(int nThreads, Executor executor) {
// Linux 中使用 EPollSelectorProvider
this(nThreads, executor, SelectorProvider.provider());
}
SelectorProvider.provider() 在不同系统中返回的对象也不相同。在 Linux 系统中返回 EPollSelectorProvider
对象。
public NioEventLoopGroup(
int nThreads, Executor executor, final SelectorProvider selectorProvider) {
this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
新增形参 DefaultSelectStrategyFactory
。
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
新增形参 RejectedExecutionHandler
NioEventLoopGroup
的父类是 MultithreadEventLoopGroup
,super()
进入 MultithreadEventLoopGroup
的构造函数。
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
前面可知,当我们调用的是无参构造函数时,nThreads
会默认设置为 0。当然如果你调用单行参构造函数手动指定 nThreads
为 0 是相同的效果。
这里判断,当你的线程数量 nThreads
为 0 的时候,会使用DEFAULT_EVENT_LOOP_THREADS
当作线程池的线程数量。
DEFAULT_EVENT_LOOP_THREADS
是多少呢?
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
如果没有使用 -Dio.netty.eventLoopThreads
指定线程数,则默认是 CPU 核心数的两倍。
结论:当我们创建 NioEventLoopGroup 时,不指定线程数量,系统会默认使用系统 CPU 核数 * 2
当作线程的数量。
我们上一步传递过来的 selectorProvider、拒绝策略、selectStrategyFactory 被封装为参数数组 args
,并分别放在 args[0]
,args[1]
,args[2]
的位置!
调用父类 MultithreadEventExecutorGroup
构造函数:
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
新增形参 DefaultEventExecutorChooserFactory
。
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
checkPositive(nThreads, "nThreads");
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
// Let the caller handle the interruption.
Thread.currentThread().interrupt();
break;
}
}
}
}
}
chooser = chooserFactory.newChooser(children);
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
上面这个 MultithreadEventExecutorGroup
构造函数就是创建 NioEventLoopGroup
对象的主要逻辑。下面我们分多个部分讲解。
核心逻辑
创建 Executor
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
如果没有指定 Executor
,EventLoopGroup
会默认创建一个 ThreadPerTaskExecutor
对象。正如 ThreadPerTaskExecutor
的名称所示,每次都会创建一个新的线程去执行任务。
创建 ThreadPerTaskExecutor
对象需要传入一个线程工厂对象 ThreadFactory
。
newDefaultThreadFactory()
protected ThreadFactory newDefaultThreadFactory() {
return new DefaultThreadFactory(getClass());
}
getClass()
返回的是 EventLoopGroup
类的 Class 对象。
ThreadPerTaskExecutor
ThreadPerTaskExecutor
类非常简单。
public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
this.threadFactory = ObjectUtil.checkNotNull(threadFactory, "threadFactory");
}
@Override
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
}
当我们调用 execute
向 ThreadPerTaskExecutor
提交一个任务时,会通过线程工厂(DefaultThreadFactory
)创建了一个新的线程,start
方法启动线程执行任务。
DefaultThreadFactory.newThread()
@Override
public Thread newThread(Runnable r) {
//创建一个线程 每次执行任务的时候都会创建一个线程实体
Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
try {
if (t.isDaemon() != daemon) {
t.setDaemon(daemon);
}
if (t.getPriority() != priority) {
t.setPriority(priority);
}
} catch (Exception ignored) {
// Doesn't matter even if failed to set.
}
return t;
}
使用 FastThreadLocalRunnable
包装原生 Runnable
。
我们继续跟进到 newThread
方法:
protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(threadGroup, r, name);
}
FastThreadLocalThread
是 Nnetty 自定义的线程类,继承自 java.lang.Thread
。
创建 EventExecutor
我们知道 NioEventLoopGroup
管理一组 NioEventLoop
。因此需要一个容器去存放这些 NioEventLoop
。
NioEventLoopGroup
的 children
字段就是那个容器,它是一个数组类型。之所使用数组类型,就是为了能进行高效的访问。
数组长度由 nThreads
指定。下面 for 循环创建 NioEventLoop
,并放入 children
数组中进行统一管理。
children = new EventExecutor[nThreads]; // 使用数组存放EventExecutors
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
children[i] = newChild(executor, args); // 核心,子类实现
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
// 中途创建失败,关闭之前创建成功的EventExecutor
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
// Let the caller handle the interruption.
Thread.currentThread().interrupt();
break;
}
}
}
}
}
MultithreadEventExecutorGroup##newChild
由子类实现,因此我们进入 NioEventLoopGroup##newChild
方法:
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
}
传递过来的 args
参数数组长度为 3,分别为:
args[0]
:selectorProviderargs[1]
:RejectedExecutionHandlerargs[2]
:SelectStrategyFactory
因此 arg[3] = queueFactory
为 null, 然后我们再重点关注 NioEventLoop
对象,可以看出,newChild
方法返回的是 NioEventLoop
对象。那么印证了我们之前的结论,NioEventLoopGroup
创建并管理一组 NioEventLoop
对象!
创建 EventExecutorChooser
chooser = chooserFactory.newChooser(children);
默认 chooserFactory
是 DefaultEventExecutorChooserFactory
类型。
我们进入到 DefaultEventExecutorChooserFactory##newChooser
中,并将上一步填充好的数组作为参数:
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
NioEventLoopGroup
管理一组 NioEventLoop
。EventExecutorChooser
就是 NioEventLoopGroup
用来获取它管理的 NioEventLoop
的。
这里根据数组的长度(即 NioEventLoop
的数量)是否是 2 的幂次方,进行不同的处理:
- 是 2 的幂次方:创建
PowerOfTwoEventExecutorChooser
。 - 不是2 的幂次方:创建
GenericEventExecutorChooser
PowerOfTwoEventExecutorChooser
PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
GenericEventExecutorChooser
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
PowerOfTwoEventExecutorChooser
和 GenericEventExecutorChooser
的功能相同。都是循环从数组中获取 NioEventLoop
,当到达数组末尾,重新回到数组头部重新获取。通过模运算让数组达到环形数组的效果。
它们的区别是:如果数组长度是 2 的幂次方,则可以使用位运算 &
实现模运算;否则,只能使用 %
来实现模运算。
我们知道,CPU 最擅长处理的就是位运算。因此这算是 Netty 做的一点优化。
注册关闭监听器
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
上面代码逻辑分为两部分:
- 创建关闭监听器
terminationListener
。 - 将监听器注册到每一个
NioEventLoop
中。
只读 Set 容器
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
上面知道,我们为了性能使用数组存放 NioEventLoop
,但是数组不方便迭代遍历,且容易遭到篡改。因此我们再创建一个只读的 Set 容器 readonlyChildren
存放 NioEventLoop
,用来进行迭代遍历。