vlambda博客
学习文章列表

全网最通俗易懂的netty源码--EventLoop的实例化

       前面的group的构造中,创建了一个children数组,然后循环为每个元素调用下面这行代码去实例化NioEventLoop。

children[i] = newChild(executor, args);

这行代码当时并没有详细分析,只是以一个概括的方式描述了它的作用。因为逻辑也是有点复杂的,所以另开一篇文章进行分析。newChild(…)方法具体如下:

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]);
}

       它调用了 NioEventLoop 的构造方法:

NioEventLoop

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider 
             selectorProvider,SelectStrategy strategy, 
             RejectedExecutionHandler rejectedExecutionHandler) {
    super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
    if (selectorProvider == null) {
        throw new NullPointerException("selectorProvider");
    }
    if (strategy == null) {
        throw new NullPointerException("selectStrategy");
    }
    provider = selectorProvider;
    //开启 NIO 中最重要的组件:Selector
    final SelectorTuple selectorTuple = openSelector();
    selector = selectorTuple.selector;
    unwrappedSelector = selectorTuple.unwrappedSelector;
    selectStrategy = strategy;
}
  • NioEventLoopGroup 代表线程池,NioEventLoop 就是其中的线程。

  • NioEventLoopGroup 是线程 NioEventLoop 的 parent,从上面代码的取名可以看出。

  • 每个 NioEventLoop 都有 Selector,上面代码也反应了这一点,这和 Tomcat 的 NIO 模型有点区别。

  • executor、selectStrategy和rejectedExecutionHandler从NioEventLoopGroup中一路传到了 NioEventLoop 中。

       结合构造函数看看NioEventLoop 类的属性:

  • provider:由 NioEventLoopGroup传进来,一个线程池有一个 selectorProvider,用于创建 Selector 实例

  • selector:线程池中每个线程都有一个 Selector 实例。

  • selectStrategy:select 操作的策略,这个不急。

  • ioRatio:IO 任务的执行时间比例,每个线程既有 IO 任务,也有非 IO 任务,该参数为了保证有足够时间是给 IO 的。

       上面的构造方法点击super,进入如下代码:

SingleThreadEventLoop

protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
                                boolean addTaskWakesUp, int maxPendingTasks,
                                RejectedExecutionHandler rejectedExecutionHandler)
 
{
    super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
    //直接忽略
    tailTasks = newTaskQueue(maxPendingTasks);
}

       继续调用父类的构造函数

SingleThreadEventExecutor

protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
                                    boolean addTaskWakesUp, int maxPendingTasks,
                                    RejectedExecutionHandler rejectedHandler) {
    //设置了parent,也就是之前创建的NioEventLoopGroup实例
    //NioEventLoop有个属性叫parent,这里就是将该属性和NioEventLoopGroup实例关联起来
    super(parent);
    this.addTaskWakesUp = addTaskWakesUp;
    this.maxPendingTasks = Math.max(16, maxPendingTasks);
    //NioEventLoopGroup构造时实例化了一个ThreadPerTaskExecutor,用来开启 NioEventLoop中的    
    //(Thread实例)
    this.executor = ObjectUtil.checkNotNull(executor, "executor");
    //taskQueue,任务队列很重要,提交给NioEventLoop的任务都会进入到taskQueue中等待
    //这个queue的默认容量是16
    taskQueue = newTaskQueue(this.maxPendingTasks);
    rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, 
                                                       "rejectedHandler");
}

       NioEventLoop的父类是SingleThreadEventLoop,而SingleThreadEventLoop的父类是 SingleThreadEventExecutor,从名字可以看出,而且是单线程的线程池(Executor,线程池)。这就有意思了,线程池NioEventLoopGroup的每个线程NioEventLoop也是一个线程池来。(之所以用线程池:主要是为了用线程池的队列。单线程也避免了多线程的切换带了的性能损失和加锁同步等麻烦)
       taskQueue:任务队列,NioEventLoop需要处理IO和非IO事件,通常它都在执行selector的select方法或者正在处理selectedKeys,如果我们要给它submit一个任务,任务就会被放到taskQueue中,等待轮询。
       rejectedExecutionHandler:taskQueue 的默认容量是 16,所以,如果 submit 的任务堆积了到了 16,再往里面提交任务会触发 rejectedExecutionHandler 的执行策略。默认的抛出RejectedExecutionException 异常

private static final RejectedExecutionHandler REJECT = new RejectedExecutionHandler() {
    @Override
    public void rejected(Runnable task, SingleThreadEventExecutor executor) {
        throw new RejectedExecutionException();
    }
};

       再回到NioEventLoop的构造方法:

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider 
             selectorProvider,SelectStrategy strategy, 
             RejectedExecutionHandler rejectedExecutionHandler) {
    //我们刚刚说完了这个
    super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
    if (selectorProvider == null) {
        throw new NullPointerException("selectorProvider");
    }
    if (strategy == null) {
        throw new NullPointerException("selectStrategy");
    }
    provider = selectorProvider;
    //开启NIO中最重要的组件:Selector
    final SelectorTuple selectorTuple = openSelector();
    selector = selectorTuple.selector;
    unwrappedSelector = selectorTuple.unwrappedSelector;
    selectStrategy = strategy;
}

这一行代码我们在上面花了大篇幅讲解

super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);

       剩下的内容就比较简单了,主要看这两行

final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector;

       这两行代码就是在创建Selector,很简单。

总结

1、将NioEventLoop实例的parent,和对应的NioEventLoopGroup实例关联起来,其实就是赋值。
2、将NioEventLoopGroup构造时定义的executor传入进来,赋值给NioEventLoop实例
3、创建一个taskQueue,默认容量为16
4、创建Selector组件
5、上面这几做好之后,一个NioEventLoop实例也就创建好了,返回并赋值给group的children数组。