Netty4.x 源码实战系列(五):深入浅出学NioEventLoopGroup
我们都知道Netty的线程模型是基于React的线程模型,并且我们都知道Netty是一个高性能的NIO框架,那么其线程模型必定是它的重要贡献之一。
在使用netty的服务端引导类ServerBootstrap或客户端引导类Bootstrap进行开发时,都需要通过group属性指定EventLoopGroup, 因为是开发NIO程序,所以我们选择NioEventLoopGroup。
接下来的两篇文章,我将从源码角度为大家深入浅出的剖析Netty的React线程模型工作机制。
本篇侧重点是NioEventLoopGroup。
首先我们先回顾一下,服务端初始化程序代码(省略非相关代码):
EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup); ... // 已省略非相关代码 // 侦听8000端口 ChannelFuture f = b.bind(8000).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); }
在分析源码之前,我们先看看NioEventLoopGroup的类继承结构图:
初始化bossGroup及workerGroup时,使用了NioEventLoopGroup的无参构造方法,本篇将从此无参构造入手,详细分析NioEventLoopGroup的初始化过程。
首先我们看看NioEventLoopGroup的无参构造方法:
public NioEventLoopGroup() { this(0); }
其内部继续调用器构造方法,并指定线程数为0:
public NioEventLoopGroup(int nThreads) { this(nThreads, (Executor) null); }
继续调用另一个构造方法,指定线程为0,且Executor为null:
public NioEventLoopGroup(int nThreads, Executor executor) { this(nThreads, executor, SelectorProvider.provider()); }
在此构造中,它会指定selector的辅助类 "SelectorProvider.provider()",我们继续查看它的调用:
public NioEventLoopGroup( int nThreads, Executor executor, final SelectorProvider selectorProvider) { this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE); }
此构造方法中,初始化了一个默认的选择策略工厂,用于生成select策略:
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory) { super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject()); }
经过上面一系列的构造方法调用,此时个参数值对应如下:
nThreads:0
executor: null
selectorProvider: SelectorProvider.provider()
selectStrategyFactory: DefaultSelectStrategyFactory.INSTANCE
以及指定了拒绝策略RejectedExecutionHandlers.reject()
接着其会调用父类MultithreadEventLoopGroup的MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args)构造方法
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) { super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args); }
此构造方法主要做了一件事,就是当指定的线程数为0时,使用默认的线程数DEFAULT_EVENT_LOOP_THREADS,此只是在MultithreadEventLoopGroup类被加载时完成初始化
private static final int DEFAULT_EVENT_LOOP_THREADS; static { DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt( "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); if (logger.isDebugEnabled()) { logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS); } }
所以根据代码,我们得出,如果初始化NioEventLoopGroup未指定线程数时,默认是CPU核心数*2
接着继续调用父类MultithreadEventExecutorGroup的MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args)构造方法
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) { this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args); }
在此构造方法中,我们指定了一个EventExecutor的选择工厂DefaultEventExecutorChooserFactory,此工厂主要是用于选择下一个可用的EventExecutor, 其内部有两种选择器, 一个是基于位运算,一个是基于普通的轮询,它们的代码分别如下:
基于位运算的选择器PowerOfTwoEventExecutorChooser
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser { private final AtomicInteger idx = new AtomicInteger(); private final EventExecutor[] executors; PowerOfTwoEventExecutorChooser(EventExecutor[] executors) { this.executors = executors; } @Override public EventExecutor next() { return executors[idx.getAndIncrement() & executors.length - 1]; } }
基于普通轮询的选择器GenericEventExecutorChooser
private static final class GenericEventExecutorChooser implements EventExecutorChooser { private final AtomicInteger idx = new AtomicInteger(); private final EventExecutor[] executors; GenericEventExecutorChooser(EventExecutor[] executors) { this.executors = executors; } @Override public EventExecutor next() { return executors[Math.abs(idx.getAndIncrement() % executors.length)]; } }
我们接着回到刚刚的构造器,其内部会继续调用MultithreadEventExecutorGroup的另一个构造方法,此构造方法是NioEventLoopGroup的核心代码
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) { // 为了便于代码剖析,以省略非相关代码 // 初始化executor if (executor == null) { executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()); } // 初始化EventExecutor children = new EventExecutor[nThreads]; for (int i = 0; i < nThreads; i ++) { children[i] = newChild(executor, args); } // 生成选择器对象 chooser = chooserFactory.newChooser(children); }
此构造方法主要做了三件事:
1、初始化executor为ThreadPerTaskExecutor的实例:
通过前面的构造方法调用,我们知道executor为null,所以在此构造方法中,executor会被初始化为ThreadPerTaskExecutor实例。我们看一下ThreadPerTaskExecutor的源码:
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(); } }
通过ThreadPerTaskExecutor 的代码发现,ThreadPerTaskExecutor 实现了Executor接口,其内部会通过newDefaultThreadFactory()指定的默认线程工厂来创建线程,并执行相应的任务。
2、初始化EventExecutor数组children
在MultithreadEventExecutorGroup的构造方法中我们看到,EventExecutor数组children初始化时是通过newChild(executor, args)实现的,而newChild的在MultithreadEventExecutorGroup中是个抽象方法
protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;
根据最开始的类继承结构图,我们在NioEventLoopGroup中找到了newChild的实现
@Override protected EventLoop newChild(Executor executor, Object... args) throws Exception { return new NioEventLoop(this, executor, (SelectorProvider) args[0], ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]); }
所以从此newChild的实现中,我们可以看出MultithreadEventExecutorGroup的children,其实就是对应的一组NioEventLoop对象。 关于NioEventLoop下一篇会作详细介绍。
3、根据我们指定的选择器工厂,绑定NioEventLoop数组对象
chooser = chooserFactory.newChooser(children);
在前面的构造方法中,我们指定了chooserFactory为DefaultEventExecutorChooserFactory,在此工厂内部,会根据children数组的长度来动态选择选择器对象,用于选择下一个可执行的EventExecutor,也就是NioEventLoop。
@Override public EventExecutorChooser newChooser(EventExecutor[] executors) { if (isPowerOfTwo(executors.length)) { return new PowerOfTwoEventExecutorChooser(executors); } else { return new GenericEventExecutorChooser(executors); } }
至此,NioEventLoopGroup初始化完成了。
通过上面的代码分析,在NioEventLoopGroup初始化的过程中,其实就是初始化了一堆可执行的Executor数组,然后根据某种chooser策略,来选择下一个可用的executor。
我们再回顾总结一下:
1、NioEventLoopGroup初始化时未指定线程数,那么会使用默认线程数,
即 线程数 = CPU核心数 * 2;
2、每个NioEventLoopGroup对象内部都有一组可执行的NioEventLoop(NioEventLoop对象内部包含的excutor对象为ThreadPerTaskExecutor类型)
3、每个NioEventLoopGroup对象都有一个NioEventLoop选择器与之对应,其会根据NioEventLoop的个数,动态选择chooser(如果是2的幂次方,则按位运算,否则使用普通的轮询)
所以通过上面的分析,我们得出NioEventLoopGroup主要功能就是为了选择NioEventLoop,而真正的重点就在NioEventLoop中,它是整个netty线程执行的关键。
下一篇我们将详细介绍NioEventLoop,敬请期待。