• 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源码剖析

  • 相关阅读:
    面试题-酷家乐面试准备
    英语学习-第一次申请试译的小透明(未完待续)
    操作系统educative版本-笔记1
    周末日记-第一次相对正规的技术教学
    资料推荐-一个神奇的网站educative.io
    面试题-持续集成
    每天5分钟玩转容器技术-读书笔记-第六章
    每天5分钟玩转容器技术-读书笔记-第四章到第五章
    工作日记-文件柜驱动层开发总结
    DP套题练习1
  • 原文地址:https://www.cnblogs.com/scikstack/p/13524334.html
Copyright © 2020-2023  润新知