空山新雨后,天气晚来秋。今年的秋天好像来的格外早,天气渐凉,大家注意保暖。上篇文章主要为大家介绍了下Netty服务端绑定端口以及注册OP_ACCEPT事件流程,今天就继续为大家分析下服务端启动流程中的关键类NioEventLoopGroup源码实现。
一、温故知新
public void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap server = new ServerBootstrap().group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
.childHandler(new HeartBeatServerChannelInitializer());
try {
ChannelFuture future = server.bind(this.port).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
前文讲过,ServerBootstrap启动的过程中,会为启动类ServerBootstrap设置两个线程组,分别为boss线程和worker线程,一般来说,boss线程为1个,worker线程数为cpu核数x2,下面就让我们来看看,NioEventLoopGroup在服务端初始化的流程中到底起什么作用吧。
二、NioEventLoopGroup解析
通过类图可以看到,NioEventLoopGroup父类为MultithreadEventLoopGroup,父类继承了抽象类MultithreadEventExcutorGroup实现了EventLoopGruop。初始化NioEventLoopGroup的时候,会调用父类的构造方法,决定生成多少个NioEventLoop线程,默认是CPU核数的两倍。
private static final int DEFAULT_EVENT_LOOP_THREADS;
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
}
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
MultithreadEventLoopGroup最终是调用的父类MultithreadEventExecutorGroup构造函数进行初始化,同时,大部分功能也都是直接调用的父类方法,下面就让我们来看下MultithreadEventExecutorGroup的实现
三、MultithreadEventExecutorGroup解析
3.1. 初始化函数
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
checkPositive(nThreads, "nThreads");
if (executor == null) {
// 获取线程执行器executor和线程工厂
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
// 根据线程数构建EventExecutor数组
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
// 初始化线程组中的线程,通过NioEventLoopGroup创建NioEventLoop实例
children[i] = newChild(executor, args);
success = true;
} catch (Exception 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);
}
}
};
// 为每个EventLoop添加终止监听器
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
// 创建只读副本
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
3.2. newChild实现
查看newChild方法,实际上是个抽象方法,查看其实现如下,可以看到对应多种实现。
以本文主要分析的NioEventLoopGroup实现为例,查看其实现如下:
@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);
}
创建NioEventLoop时,一共有个6个参数
- 第一个为NioEventLoopGroup自身
- 第二个为线程执行器,用于启动线程
- 第三个参数为Nio的Selector选择器的提供者
- 第四个参数主要在NioEventLoop的run方法中用于控制选择循环
- 第五个参数为非I/O任务提交被拒绝时的处理Handler
- 第六个参数为队列工厂,在NioEventLoop中,队列读是单线程操作,队列写则可能是多线程操作,默认为MpscChunkedArrayQueue
3.3. newChooser解析
newChooser方法,最终由DefaultEventExecutorChooserFactory实现,查看源码如下:
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
根据线程是否是2的幂次来选择策略,是的话选择PowerOfTwoEventExecutorChooser,否则选择GenericEventExecutorChooser。二者的区别在于,前者通过与运算计算下一个选择的线程组index,后者是通过求余的方式计算下一个线程在线程组的index。其中与运算的性能会更好一点,因此我们在设置worker线程数的时候如果想要自定义,尽量设置为2的倍数。
3.4. next方法分析
@Override
public EventExecutor next() {
return chooser.next();
}
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
private static final class GenericEventExecutorChooser implements EventExecutorChooserFactory.EventExecutorChooser {
public EventExecutor next() {
return this.executors[(int)Math.abs(this.idx.getAndIncrement() % (long)this.executors.length)];
}
}
通过next方法获取NioEventLoop线程,最终根据不同的选择器,选择不同的实现进行选择,这里就不赘述了。
四、小结
本文主要分析了Netty启动时用到的组件NioEventLoopGroup实现原理,通过类图发现,除了NioEventLoopGroup线程组,Netty还提供了一套由epoll模型实现的EpollEventLoopGroup,以及其他I/O多路复用模型线程组,本文主要分析NioEventLoopGroup实现,其他线程组后面有机会在详细比对下。总结来说,NioEventLoopGroup其实主要完成以下三件事:
- 创建一定数量的NioEventLoop线程
- 创建线程选择器chooser,通过选择器来获取线程
- 创建线程工厂并构建线程执行器