NioEventLoop的启动时机是在服务端的NioServerSocketChannel中的ServerSocketChannel初始化完成,且注册在NioEventLoop后执行的, 下一步就是去绑定端口,但是在绑定端口前,需要完成NioEventLoop的启动工作, 因为程序运行到这个阶段为止,依然只有MainThread一条线程,下面就开始阅读源码看NioEventLoop如何开启新的线程自立家门的
总想说 NioEventLoop的整体结构,像极了这个图
该图为,是我画的NioEventLoop启动的流程草图,很糙,但是不画它总觉的少了点啥
NioEventLoop的继承体系图
NioEventLoop的线程开启之路程序的入口是AbstractBootStrap, 这个抽象的启动辅助类, 找到它准备绑定端口的doBind0()方法,下面是源码:
private static void doBind0( final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) { // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up // the pipeline in its channelRegistered() implementation. // todo 此方法在触发 channelRegistered() 之前调用, 给用户一个机会,在 channelRegistered() 中设置pipeline // todo 这是 eventLoop启动的逻辑 , 下面的Runable就是一个 task任务, 什么任务的呢? 绑定端口 // todo 进入exeute() System.out.println("00000"); channel.eventLoop().execute(new Runnable() { @Override public void run() { if (regFuture.isSuccess()) { // todo channel绑定端口并且添加了一个listenner channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } else { promise.setFailure(regFuture.cause()); } } }); }我们关注上面的channel.execute(Runable)方法, 如果我们直接使用鼠标点击进去,会进入java.util.concurrent包下的Executor接口, 原因是因为,它是NioEventLoop继承体系的超顶级接口,见上图, 我们进入它的实现类,SingleThreadEventExcutor, 也就是NioEventLoop的间接父类, 源码如下:
// todo eventLoop事件循环里面的task,会在本类SingleThreadEventExecutor里面: execute() 执行 @Override public void execute(Runnable task) { if (task == null) { throw new NullPointerException("task"); } // todo 同样判断当前线程是不是 eventLoop里面的那条唯一的线程, 如果是的话, 就把当前任务放到任务队列里面等着当前的线程执行 // todo ,不是的话就开启新的线程去执行这个新的任务 // todo , eventLoop一生只会绑定一个线程,服务器启动时只有一条主线程,一直都是在做初始化的工作,并没有任何一次start() // todo 所以走的是else, 在else中首先开启新的线程,而后把任务添加进去 boolean inEventLoop = inEventLoop(); if (inEventLoop) { addTask(task); } else { // todo 开启线程 , 进入查看 startThread(); // todo 把任务丢进队列 addTask(task); if (isShutdown() && removeTask(task)) { reject(); } } if (!addTaskWakesUp && wakesUpForTask(task)) { wakeup(inEventLoop); } }现在执行这些代码的线程依然是主线程,主线程手上有绑定端口任务,但是它想把这个任务提交给NioEventLoop去执行,于是它就做出下面的判断
boolean inEventLoop = inEventLoop(); // 方法实现 @Override public boolean inEventLoop(Thread thread) { return thread == this.thread; }但是发现,主线程并不是NioEventLoop唯一绑定的那个线程, 于是他就准备下面两件事:
开启激活当前NioEventLoop中的线程
把绑定端口的任务添加到任务队列
开启新线程的逻辑在下面,我删除了一些收尾,以及判断的代码,保留了主要的逻辑
private void doStartThread() { assert thread == null; // todo 断言线程为空, 然后才创建新的线程 executor.execute(new Runnable() { // todo 每次Execute 都是在使用 默认的线程工厂,创建一个线程并执行 Runable里面的任务 @Override public void run() { // todo 获取刚才创建出来的线程,保存在NioEventLoop中的 thread 变量里面, 这里其实就是在进行那个唯一的绑定 thread = Thread.currentThread(); updateLastExecutionTime(); try { // todo 实际启动线程, 到这里 NioEventLoop 就启动完成了 SingleThreadEventExecutor.this.run(); } }主要做了两件事第一波高潮来了 1. 调用了NioEventLoop的线程执行器的execute,这个方法的源码在下面,可以看到,excute,其实就是在创建线程, 线程创建完成后,立即把新创建出来的线程当作是NioEventLoop相伴终生的线程;
public final class ThreadPerTaskExecutor implements Executor { private final ThreadFactory threadFactory; public ThreadPerTaskExecutor(ThreadFactory threadFactory) { if (threadFactory == null) { throw new NullPointerException("threadFactory"); } this.threadFactory = threadFactory; } // todo 必须实现 Executor 里面唯一的抽象方法, execute , 执行性 任务 @Override public void execute(Runnable command) { threadFactory.newThread(command).start(); } }