7.给大动脉来一刀-NioEventLoop 源码分析

4.不完全的 NioEventLoopGroup & NioEventLoop 源码分析 文章中, 只是简单的说了一下 NioEventLoop 的继承, 以及实例化过程. 并没有对线程的开启以及任务的执行等有解释, 本篇主要来看这些.

创建 EventLoop 线程

EventLoop 线程的创建是通过调用 SingleThreadEventExecutor#execute 方法, 来创建.

private volatile Thread thread;
private final Queue<Runnable> taskQueue;
private volatile int state = ST_NOT_STARTED;

public void execute(Runnable task) {
    ObjectUtil.checkNotNull(task, "task");
    execute(task, !(task instanceof LazyRunnable) && wakesUpForTask(task));
}
// 参数 task: 表示要执行的任务.
// 参数 immediate: 表示是否立即执行.
private void execute(Runnable task, Boolean immediate) {
    // 判断当前调用 execute 方法的线程, 是否和 thread 变量所保存的是同一对象.
    // 如果是则返回 true, 否则返回 false.
    Boolean inEventLoop = inEventLoop();
    // 将任务添加到 taskQueue 队列.
    addTask(task);
    if (!inEventLoop) {
        // 下面有解释.
        startThread();
        // state >= ST_SHUTDOWN(4) 说明已经被关闭.
        if (isShutdown()) {
            Boolean reject = false;
            try {
                // 任务在关闭之前执行完成, 会返回 false.
                // 当移除成功, 说明该任务无法执行, 所以就会抛出下面的异常.
                if (removeTask(task)) {
                    reject = true;
                }
            } catch (UnsupportedOperationException e) {
            }
            // 抛出 RejectedExecutionException("event executor terminated") 异常.
            if (reject) {
                reject();
            }
        }
    }
    if (!addTaskWakesUp && immediate) {
         // 下面的 解释1.
        wakeup(inEventLoop);
    }
}

解释1: 要想执行 wakeup 方法, 就必须要 immediate = true 也就是说要立即执行, 但对于 addTaskWakesUp 变量它永远都是 false.

通过上面的代码可以知道 execute 方法会在当前线程或其它线程中被调用. 而在子类 NioEventLoop 重写了该方法.

protected void wakeup(boolean inEventLoop) {
    // inEventLoop 必须要是 false, 保证是在另一个线程中被调用.
    // nextWakeupNanos 中保存了超时时间. 也就是说必须要调用了 select 方法.
    if (!inEventLoop && nextWakeupNanos.getAndSet(AWAKE) != AWAKE) {
        // java.nio.channels.Selector
        selector.wakeup();
    }
}

至于为啥要这样操作, 个人认为当前的 EventLoop 已经调用了 select() 方法并阻塞了, 而另一个 EventLoop 向当前的 EventLoop 中添加了任务, 这个任务又要立即执行, 所以会调用 selector.wakeup() 方法, 让 select() 立即返回, 才能执行该任务.

如果是通过当前的 EventLoop 添加的任务, 就不用调用 selector.wakeup() 方法了, 因为 select() 方法肯定已经返回了.

下面是最主要的 doStartThread() 方法, 对于 startThread 方法无非就是判断线程有没有开启, 修改状态值.

private void startThread() {
    // state 变量的默认值为 ST_NOT_STARTED(1).
    // 这里就说明还没有开始.
    if (state == ST_NOT_STARTED) {
        // 通过 CAS 操作, 将 state 变量值改为 ST_STARTED(2), 说明已经开始了.
        // 使用 CAS 操作, 是为了防止其它线程已经执行过下面代码了.
        // 另外 ST_NOT_STARTED 的值为 1.
        if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
            Boolean success = false;
            try {
                // 下面有解释.
                doStartThread();
                success = true;
            }
            finally {
                if (!success) {
                    // 启动线程失败, 要对状态进行还原.
                    STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
                }
            }
        }
    }
}

private final Executor executor;

private void doStartThread() {
    assert thread == null;
    // executor 变量, 查看下面 解释2.
    executor.execute(new Runnable() {
        @Override
        // FastThreadLocalThread 类型的线程被启动时, 会执行该方法. 
        public void run() {
            // 这里肯定是新创建的线程执行的该方法, 所以就直接将该线程和当前的 EventLoop 绑定.
            thread = Thread.currentThread();
            if (interrupted) {
                thread.interrupt();
            }
            Boolean success = false;
            // 更新上次执行时间.
            updateLastExecutionTime();
            try {
                // 这里执行 run 方法是在子类 NioEventLoop 实现的.
                // 该方法是整个 NioEventLoop 核心任务.
                SingleThreadEventExecutor.this.run();
                success = true;
            }
            catch (Throwable t) {
                // ...
            }
            finally {
               // ...
            }
        }
    }
    );
}

解释2: 实例化 NioEventLoop 时, 会调用下面这个父类有参构造方法.

protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
                                    boolean addTaskWakesUp, Queue<Runnable> taskQueue,
                                    RejectedExecutionHandler rejectedHandler) {

    this.executor = ThreadExecutorMap.apply(executor, this);
    // ...
}

该构造方法中的参数 executor 的类型为 ThreadPerTaskExecutor, 至于为什么是这个类型是在 4.不完全的 NioEventLoopGroup & NioEventLoop 源码分析 中有说.

其实也就是调用了, ThreadPerTaskExecutor#execute 在该方法内部会调用 ThreadFactory#newThread 来创建一个线程并启动.

值得注意的是: EventLoop 中的线程都是 FastThreadLocalThread 类型.

NioEventLoop 核心任务

    protected void run() {
        int selectCnt = 0;
        for (;;) {
            try {
                int strategy;
                try {
                    /**
                     * hasTasks(): 如果有未执行的任务就返回 true.
                     * calculateStrategy: 方法会根据 hasTasks() 返回值, 
                     * 来调用 hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
                     * 
                     * 注意: selectSupplier.get() 方法最终会调用 selector.selectNow() 方法, 这样就会立即返回.
                     */
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                    // 根据 calculateStrategy 方法的实现, 该方法只会返回 -1 或 >= 0 的值.
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.BUSY_WAIT:
                        
                    case SelectStrategy.SELECT:
                        // 获取定时任务的时间(毫秒)
                        // 只会从 scheduledTaskQueue 队列中获取第一个任务的最后执行时间.
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            // 如果没有定时任务就设置为 Long.MAX_VALUE 值.
                            curDeadlineNanos = NONE;
                        }
                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            if (!hasTasks()) {
                                /**
                                 * 如果没有定时任务则直接调用 selector.select().
                                 * 如果有定时任务则,
                                 * timeoutMillis <= 0 ? selector.selectNow() : 
                                 *                      selector.select(timeoutMillis);
                                 */
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                            /**
                             * 这里只是为了防止不必要的 Selector#wakeup 方法的调用.
                             * 可以看一下上面的 解释1.
                             */
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                    default:
                    }
                } catch (IOException e) {
                    rebuildSelector0();
                    selectCnt = 0;
                    handleLoopException(e);
                    continue;
                }

                selectCnt++;
                cancelledKeys = 0;
                needsToSelectAgain = false;
                // ioRatio 默认值是 50.
                final int ioRatio = this.ioRatio;
                boolean ranTasks;
                // ioRatio 无论是不是 100, 都会先执行 IO 事件以及任务.
                if (ioRatio == 100) {
                    try {
                        if (strategy > 0) {
                            processSelectedKeys();
                        }
                    } finally {
                        ranTasks = runAllTasks();
                    }
                } else if (strategy > 0) {
                    // IO 操作开始时间(纳秒)
                    final long ioStartTime = System.nanoTime();
                    try {
                        // 执行 ChannelHandler
                        processSelectedKeys();
                    } finally {
                        // IO 操作结束时间.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                } else { // 没有监听到任何事件就执行任务.
                    ranTasks = runAllTasks(0);
                }

                if (ranTasks || strategy > 0) {
                    if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                                selectCnt - 1, selector);
                    }
                    selectCnt = 0;
                  
                  // 该方法作用还不清楚
                } else if (unexpectedSelectorWakeup(selectCnt)) {
                    selectCnt = 0;
                }
            } catch (CancelledKeyException e) {
                if (logger.isDebugEnabled()) {
                    logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                            selector, e);
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
            try {
                if (isShuttingDown()) {
                    closeAll();
                    if (confirmShutdown()) {
                        return;
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
        }
    }

ioRatio 无论是不是 100, 都会先执行 IO 事件以及任务. 主要的区别是 taskQueue 任务队列的执行时间, 也就是说不管在这个时间段内队列任务有没有执行完, 都不会再执行了.

值得注意的是:
1.不管任务队列的执行时间是多少, tailTasks 尾部队列的所有任务都会执行.
2.如果说 runAllTasks 方法参数为 0, 那么只会执行 64 个任务后就不会再执行.

processSelectedKeys

private void processSelectedKeys() {
    if (selectedKeys != null) {
        processSelectedKeysOptimized();
    } else { // 该变量为 null 只有在 NioEventLoop#openSelector 方法失败后.
        processSelectedKeysPlain(selector.selectedKeys());
    }
}

private void processSelectedKeysOptimized() {
    // 这里是个 for 循环, 会循执行所有的 Channel.
    for (int i = 0; i < selectedKeys.size; ++i) {
        final SelectionKey k = selectedKeys.keys[i];
        selectedKeys.keys[i] = null;
        // 获取绑定对象, 可以理解为 附件.
        // 该值为之前绑定的 NioServerSocketChannel 或 NioSocketChannel.
        final Object a = k.attachment();
        
        // AbstractNioChannel 是一个抽象类,
        // NioServerSocketChannel 和 NioSocketChannel 都属于该抽象类的实现.
        if (a instanceof AbstractNioChannel) {
            /**
             * 根据 SelectionKey 的事件, 调用 AbstractNioChannel#unsafe() 中的不同方法.
             * 例如 OP_READ 或 OP_ACCEPT 都会调用 read 方法.
             * 剩下的就可以根据 7. 给大动脉来一刀-ChannelPipeLine, 来看查看事件传播了.
             */
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
            // 这里没遇到过, 等遇到了再补充.
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }
        
        /**
         * 什么情况下可以 需要再次选择.
         */
        if (needsToSelectAgain) {
            // 清空 selectedKeys 数组.
            selectedKeys.reset(i + 1);
            // 这里再调用一次 selector.selectNow(),
            // 保证这段期间没有已准备好的通道了.
            selectAgain();
            i = -1;
        }
    }
}

runAllTasks

该方法分为有参和无参, 先来看无参. 只要执行了 taskQueue 队列中的任务该方法就返回 true 否则就返回 false.

看该方法前推荐先看一下方法:

  • fetchFromScheduledTaskQueue
  • runAllTasksFrom
protected Boolean runAllTasks() {
    assert inEventLoop();
    Boolean fetchedAll;
    Boolean ranAtLeastOne = false;
    do {
        // 会把所有可执行的计划任务放到任务队列中,
        // 直到没有计划任务或没有可以执行的计划任务了, 
        fetchedAll = fetchFromScheduledTaskQueue();
        // 任务队列 中的所有任务.
        if (runAllTasksFrom(taskQueue)) {
            // 至少运行了一个任务.
            ranAtLeastOne = true;
        }
    } while (!fetchedAll);

    // 只要执行了任务就记录一下这次执行时间(System.nanoTime() - START_TIME;).
    if (ranAtLeastOne) {
        lastExecutionTime = ScheduledFutureTask.nanoTime();
    }
    // 运行 tailTasks(尾部任务) 队列中的任务.
    afterRunningAllTasks();
    return ranAtLeastOne;
}

fetchFromScheduledTaskQueue

通过方法名知道大概作用就是 从计划任务队列中获取.

private Boolean fetchFromScheduledTaskQueue() {
    /**
     * 这个就是 计划任务队列 了.
     * 先留个坑, 关于该变量的初始化和添加任务后续再说.
     */
    if (scheduledTaskQueue == null || scheduledTaskQueue.isEmpty()) {
        return true;
    }
    // 纳秒的获取最终会调用 ScheduledFutureTask.nanoTime() 方法.
    // 最终会使用 当前的纳秒时间 - ScheduledFutureTask 类加载的纳秒时间 得到一个纳秒时间并返回.
    // 代码: System.nanoTime() - START_TIME.
    long nanoTime = AbstractScheduledEventExecutor.nanoTime();
    for (;;) {
        // 根据 纳秒值 从 计划任务队列 获取一个可以执行的任务.
        Runnable scheduledTask = pollScheduledTask(nanoTime);
        if (scheduledTask == null) {
            return true;
        }
        // 将定时任务添加到 任务队列 准备执行.
        if (!taskQueue.offer(scheduledTask)) {
            // 添加失败就放回 计划任务队列 准备下一次添加.
            scheduledTaskQueue.add((ScheduledFutureTask<?>) scheduledTask);
            return false;
        }
    }
}

protected final Runnable pollScheduledTask(long nanoTime) {
    assert inEventLoop();
    // 这个方法就是用来获取 计划任务队列 中的第一个任务的.
    ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
    // 判断这个任务的执行时间.
    // 注意: 如果还没有到执行时间, 这里返回的是 null.
    if (scheduledTask == null || scheduledTask.deadlineNanos() - nanoTime > 0) {
        return null;
    }
    // 到了执行之间后才会从 计划任务队列 中移除掉第一个.
    scheduledTaskQueue.remove();
    // 如果这个任务不是循环的就直接将 deadlineNanos 设置为 0.
    scheduledTask.setConsumed();
    return scheduledTask;
}

runAllTasksFrom

protected final Boolean runAllTasksFrom(Queue<Runnable> taskQueue) {
    // 只要 任务队列 中的任务不是 WAKEUP_TASK 就会返回任务.
    // 这里返回 null 是说明 任务队列 没有任务了.
    Runnable task = pollTaskFrom(taskQueue);
    // 返回 false 表示 没有执行过任务.
    if (task == null) {
        return false;
    }
    // 循环执行所有任务.
    for (;;) {
        // 调用任务的 run 方法.
        safeExecute(task);
        // 只要 任务队列 中的任务不是 WAKEUP_TASK 就会返回任务.
        task = pollTaskFrom(taskQueue);
        // 返回 true 表示执行过任务.
        if (task == null) {
            return true;
        }
    }
}

接下来在看有参 runAllTasks 方法.

protected Boolean runAllTasks(long timeoutNanos) {
    fetchFromScheduledTaskQueue();
    // 从 任务队列 中获取一个不为 WAKEUP_TASK 的任务.
    Runnable task = pollTask();
    if (task == null) {
        // 执行尾部所有任务
        afterRunningAllTasks();
        return false;
    }
    // 计算出任务执行时间.
    // 公式: (System.nanoTime() - START_TIME) + timeoutNanos.
    final long deadline = timeoutNanos > 0 ? ScheduledFutureTask.nanoTime() + timeoutNanos : 0;
    long runTasks = 0;
    long lastExecutionTime;
    for (;;) {
        // 执行任务的 run 方法.
        safeExecute(task);
        // 运行任务的计数.
        runTasks ++;
        // 执行完 64 个任务后, 重新计算当前时间.
        if ((runTasks & 0x3F) == 0) {
            // 定时任务初始化到当前的时间
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            // 如果超过预定时间则不执行(nanoTime() 是耗时的)
            if (lastExecutionTime >= deadline) {
                break;
            }
        }
        // 从 任务队列 中获取一个不为 WAKEUP_TASK 的任务.
        task = pollTask();
        // 没有任务可以执行了后就记录这次执行时间(System.nanoTime() - START_TIME;).
        if (task == null) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            break;
        }
    }
    // 执行尾部所有任务
    afterRunningAllTasks();
    this.lastExecutionTime = lastExecutionTime;
    return true;
}

execute

execute 只能接受 Runnable 类型的任务, 而且该方法没有返回值.

bossGroup.execute(new Runnable() {
    @Override
    public void run() {
        System.out.println("Asdasdad");
    }
});

或者

bossGroup.execute(() -> {
    System.out.println("Asdasdad");
});

submit

submit 不管是 Runnable 还是Callable 类型的任务都可以接受, 但是 Runnable 类型返回值可以为 void 或通过参数指定返回值类型, 如果没有返回值则 Future 的 get() 获得的还是 null.

Future<Object> submit = bossGroup.submit(new Callable<Object>() {
    @Override
    public Object call() throws Exception {
        System.out.println("Asdasdad");
        return null;
    }
});

或者
  
Future<Object> submit = bossGroup.submit(() -> {
    System.out.println("Asdasdad");
    return null;
});

Runnable 还是 Callable 都会封装为 RunnableFuture 类型, 然后执行 execute 方法.

schedule(计划任务)

ScheduledFuture<String> schedule = bossGroup.schedule(new Callable() {
    @Override
    public Object call() throws Exception {
        return "dsfdfs";
    }
}, 10, TimeUnit.SECONDS);

会将当前的 Eventloop 和 要执行的任务, 以及要执行任务的启动时间(毫秒值), 封装成 ScheduledFutureTask; 然后调用 schedule 方法, 代码如下.

private <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
    // 如果是在同一个线程中添加
    if (inEventLoop()) {
        /**
         * 该方法会先初始化 scheduledTaskQueue 队列, 然后给任务设置ID并将任务添加到 scheduledTaskQueue 队列.
         * 该队列是 DefaultPriorityQueue 实例, 根据执行时间保证队列元素的顺序性.
         *
         * 注意: 同一个线程中添加计划任务时, 就算添加任务的时间小于当前任务的
         */
        scheduleFromEventLoop(task);
    } else {
        final long deadlineNanos = task.deadlineNanos();
        /**
         * 当前任务结束时间 < 下次任务的结束时间, 则就会将当前任务添加到 taskQueue 队列.
         *
         * 下次任务的执行时间是根据 nextScheduledTaskDeadlineNanos() 方法获取的.
         */
        if (beforeScheduledTaskSubmitted(deadlineNanos)) {
            // 把当前任务添加到 taskQueue 队列, 就是为了能让该任务立即执行.
            execute(task);
        } else {
            /**
             * 该方法也是将任务添加到 taskQueue 队列, 但并不是立即执行.
             */
            lazyExecute(task);
            /**
             * 该方法与 beforeScheduledTaskSubmitted 方法一样, 都是怕当前任务的执行时间
             * 晚于下次任务的执行时间, 所以要立马执行.
             */
            if (afterScheduledTaskSubmitted(deadlineNanos)) {
                execute(WAKEUP_TASK);
            }
        }
    }
    return task;
}

该方法添加的计划任务并不像 HashedWheelTimer 一样, 该计划任务由于使用的是 EventLoop 执行, 所以有可能会影响 IO 事件的执行.

其实这三种添加任务的方式, 无论哪一种只要任务执行时间长, 都会影响 IO 事件的执行.

executeAfterEventLoopIteration(添加尾部任务)

// 该任务会被添加到 tailTasks 队列.
SingleThreadEventLoop singleThreadEventLoop = ch.eventLoop();
singleThreadEventLoop.executeAfterEventLoopIteration(new Runnable() {
    @Override
    public void run() {
        System.out.println("别看我第一个添加, 但是我最后一个执行.");
    }
});

注意: 虽然是尾部任务, 但是当 ioRatio 不为 100 时, 有可能 taskQueue 队列中的任务没有执行完还是会执行 tailTasks 队列中的任务. 但是 tailTasks 队列中的任务都会被执行完.

参考资料

Java NIO的wakeup剖析
Java NIO系列教程(六) Selector
Netty 源码解析(七): NioEventLoop 工作流程
Netty源码学习(九) NioEventLoop的启动
Netty Reactor线程启动及执行
Netty NioEventLoop 啟動過程原始碼分析
Netty 之工作线程 NioEventLoop
NioEventLoop篇
Netty NioEventLoop 启动过程源码分析
深入理解 NioEventLoop启动流程
Netty-NioEventLoop源码剖析

posted @ 2020-08-18 16:39  scikstack  阅读(170)  评论(0编辑  收藏  举报