触发NioEventLoop启动有两个方式:
1、服务端启动绑定端口
2、新连接接入通过chooser绑定一个NioEventLoop
在这里,我们先讲解第一种方式,后续文章讲解第二种。
NioEventLoop第一种启动方式入口从用户代码bind()进入,initAndRegister()方法的后面,有一个doBind0(),进入,便看到,channel绑定的NioEventLoop的execute方法:
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
execute()做了添加线程和添加任务的事情,在添加线程里面,执行的是之前传进来的ThreadPerTaskExecutor的excute方法,这个方法实际上就是创建线程,绑定线程,执行线程:
1、thread=Thread.currentThread()
2、NioEventLoop.run()
第二个步骤下一篇文章详细讲解。现在来一步步分析:
NioEventLoop的execute方法这里:
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread();
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
inEventLoop判断给我任务的线程是不是NioEventLoop的线程,如果不是就需要调用startThread()启动线程并且绑定到NioEventLoop上面,然后把任务丢到任务队列里面;否则,直接把这个任务添加到任务队列里面(这个队列就是上一篇的MpscQueue)。在这里,给任务的线程是主线程,并且NioEventLoop的线程也没有启动,所以调用startThread()方法.
startThread()一直进入,有个doStartThread方法:
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
...
try {
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
...
}
}
});
}
这里就是调用ThreadPerTaskExecutor的execute方法,把新建的线程绑定到NioEventLoop的thread对象里面。然后有个
SingleThreadEventExecutor.this.run()方法,真正的去执行NioEventLoop的任务。
至于启动线程之后的addTask()方法,其实很简单,就是把任务加入到我们之前创建的newMpscQueue队列里面:
protected void addTask(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
if (!offerTask(task)) {
reject(task);
}
}
final boolean offerTask(Runnable task) {
if (isShutdown()) {
reject();
}
return taskQueue.offer(task);
}