NioEventLoop相对NioEventLoopGroup来说就复杂很多了,需要一定的耐心来看这篇文章。
首先从NioEventLoop的启动讲起,对于线程池来说,启动一般都是从第一个任务的添加开始的。经过跟踪,找到execute()方法在SingleThreadEventExecutor类中:
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
// inEventLoop表示启动线程与当前线程相同,相同表示已经启动,不同则有两种可能:未启动或者线程不同
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
// 运行中则直接添加任务到队列中
addTask(task);
} else {
// 尝试启动任务
startExecution();
// 将任务加到任务队列taskQueue中
addTask(task);
// 发现已经关闭则移除任务并拒绝
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
// 唤醒执行线程
wakeup(inEventLoop);
}
}
private void startExecution() {
// 未启动的状态下才进行启动
if (STATE_UPDATER.get(this) == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
// 增加一个定时任务,该任务将定时任务队列中的已取消任务从队列中移除,该任务每间隔1秒执行1次
schedule(new ScheduledFutureTask<Void>(
this, Executors.<Void>callable(new PurgeTask(), null),
ScheduledFutureTask.deadlineNanos(SCHEDULE_PURGE_INTERVAL), -SCHEDULE_PURGE_INTERVAL));
// 开始执行
scheduleExecution();
}
}
}
// 如果已经关闭了,则不能再加任务,否则加入到任务队列中
protected void addTask(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
if (isShutdown()) {
reject();
}
taskQueue.add(task);
}
简单的部分就不用讲了,我们来看看两个可能会让人疑惑的点:
1、scheduleExecution()
这个方法是将asRunnable提交到executor,由于线程池的线程数与EventExecutor的个数相同,所以可以保证每次asRunnable都能及时处理, asRunnable逻辑比较简单,执行所在类中的run方法,这个run方法是个抽象方法,它的实现有几个要求要满足:
a、run方法中只执行一定量的任务。如果执行太多,或者一直执行不跳出,那么后期netty中期望引入的fork/jion框架stealing机制就会失效或者大打折扣;
b、run方法执行完一定量任务后,本次任务完成,此时需要调用scheduleExecution(),否则该EventExecutor后面的任务将无法进行;
c、基于b中子类必须调用scheduleExecution()的要求,任务的执行必须使用try catch方式。如果不这样的话,发生任何异常都会导致EventExecutor关闭,里面的所有任务都将被清理。
另一个需要注意的点是scheduleExecution方法在执行asRunnable前将thread置为null了,该thread表示EventLoop所在线程,由于executor.execute的执行并不能保证是哪个Thread来执行,因此先把thread置为null,等进行asRunnable的run方法后再次设置thread为Thread.currentThread。
protected final void scheduleExecution() {
updateThread(null);
executor.execute(asRunnable);
}
private final Runnable asRunnable = new Runnable() {
@Override
public void run() {
updateThread(Thread.currentThread());
// lastExecutionTime must be set on the first run
// in order for shutdown to work correctly for the
// rare case that the eventloop did not execute
// a single task during its lifetime.
if (firstRun) {
firstRun = false;
updateLastExecutionTime();
}
try {
SingleThreadEventExecutor.this.run();
} catch (Throwable t) {
// 发生异常则关闭整个EventExecutor
logger.warn("Unexpected exception from an event executor: ", t);