学习netty,一项不可绕过的知识点就是其线程模型。我们知道一个EventLoop包含一个线程,EventLoopGroup管理一组线程。本文基于netty4.1.26探访何时创建、如何管理这些线程。本文以io.netty.channel.nio包下的NioEventLoopGroup类为源头,跟踪代码,忽略与线程无关的逻辑和参数。Let's go!
首先,我们调用new NioEventLoopGroup()会进入下面的逻辑:
public NioEventLoopGroup() {
this(0); //转到下面的的调用,nThreads默认为0
}
public NioEventLoopGroup(int nThreads) {
this(nThreads, (Executor) null); //转到下面的的调用,executor默认为null
}
public NioEventLoopGroup(int nThreads, Executor executor) {
this(nThreads, executor, SelectorProvider.provider()); //继续调用下面的构造函数,这里的默认参数先不管
}
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider) {
this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE); //同样,继续调用下面
}
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
//这里将调用超类的构造函数
super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
这样就进入了io.netty.channel包下的MultithreadEventLoopGroup类:
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
在这里将进入超类io.netty.util.concurrent包下的MultithreadEventExecutorGroup类,第一个参数已经不再是0,更改成了由系统参数获得的大于等于1的数,即表示的管理的线程个数(究竟是多少暂且不用关心),继续:
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
//调用下面的函数
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
if (executor == null) {
//这里是关键,用于每个EventLoop对应一个线程的关键
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
...
这里executor不再为null,更改为了io.netty.util.concurrent包下的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) {
//这里每执行一次execute创建一个线程去执行
threadFactory.newThread(command).start();
}
}
那么这里executor为ThreadPerTaskExecutor类的实例有什么用呢?继续看MultithreadEventExecutorGroup构造函数剩余的部分:
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 {
...
}
}
children为MultithreadEventExecutorGroup类的属性:
public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {
private final EventExecutor[] children;
来看一下newChild方法:
protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;
这里是抽象方法,对于不同子类有不同实现。所以回到最开始的NioEventLoopGroup类中寻找到该方法的实现:
@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]);
}
所以NioEventLoopGroup类管理的EventLoop为实际是io.netty.channel.nio包下的NioEventLoop类。下面我们重点看这个类及其超类的代码:
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
...
}
进入io.netty.channel包下的SingleThreadEventLoop类:
protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
...
}
进入io.netty.util.concurrent包下的SingleThreadEventExecutor类:
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedHandler) {
...
//executor如果为null,抛出异常,否则赋值给属性executor
this.executor = ObjectUtil.checkNotNull(executor, "executor");
...
}
至此,new NioEventLoopGroup()构造函数已经完结。这里重点关注的是这个executor属性,就是那个ThreadPerTaskExecutor类的实例。那么,究竟怎么让NioEventLoop创建一个工作线程呢?我们在io.netty.util.concurrent包下的SingleThreadEventExecutor类中找到startThread()方法:
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
try {
//进入这个方法
doStartThread();
} catch (Throwable cause) {
STATE_UPDATER.set(this, ST_NOT_STARTED);
PlatformDependent.throwException(cause);
}
}
}
}
private void doStartThread() {
//在调用这个方法时,要求管理的thread属性为null,表示还没有创建线程
assert thread == null;
//可以追溯到ThreadPerTaskExecutor的这个executor,每执行一次executor.execute就创建一个线程,
//在整个本类中,只有这个地方会调用executor.execute方法
executor.execute(new Runnable() {
@Override
public void run() {
//将属性赋值上,表示已经创建了线程
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
//这里也是关键,调用外部类的run方法,这里暂且不表,后面分析
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
...
}
}
});
}
这个私有的startThread()方法只在该类的execute方法中被调用,我们来看一下:
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
//当前执行此代码的线程是否是thread属性所指的线程
boolean inEventLoop = inEventLoop();
//将任务放入队列,以待执行
addTask(task);
if (!inEventLoop) {
startThread();
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
//这个方法其实在这个类的超类中,这里拿到一起分析
@Override
public boolean inEventLoop() {
return inEventLoop(Thread.currentThread());
}
@Override
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
至于execute方法在哪个线程被调用,这涉及更大范围的代码,这里暂且不研究。我们可以推测,要不在thread属性所指的线程上执行这个方法,要不在初始阶段还没有创建线程时候在其他线程执行这个方法。不可能在已经创建了线程的情况下,在外部线程执行这个execute方法,因为doStartThread()方法一开始有assert thread == null。
最后一个问题,SingleThreadEventExecutor.this.run()调用究竟回到了哪里??其实回到了io.netty.channel.nio包下的NioEventLoop类,我们看一下其run方法:
@Override
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
// Always handle shutdown even if the loop processing threw an exception.
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
这里的代码看看就好了,无限循环,获取事件并处理事件。有机会再研究。
总结:本文从代码跟踪的视角关注EventLoop如何管理一个线程,抛开了无关的因素,了解netty线程模型的内涵。