Netty 4.2.x 源码深度解析 (十一):EventExecutor 业务线程池 —— 防止 IO 线程阻塞的并发模型

IoEventLooprun() 循环是一个单线程的"轮询---处理---执行"三步曲,IO 事件的处理和异步任务的执行都在同一个线程中串行完成。这意味着,如果某个 ChannelHandler 的回调中执行了耗时操作(如数据库查询、远程 RPC 调用、复杂计算),整个 IoEventLoop 线程将被阻塞,该线程上所有 Channel 的 IO 事件都无法及时处理,直接导致吞吐量骤降甚至连接超时。Netty 的解决方案是 EventExecutor ------ 一组独立的业务线程池,通过为特定 ChannelHandler 绑定专用的 EventExecutor,将耗时任务从 IO 线程"卸载"到业务线程池中执行,实现 IO 线程与业务线程的隔离。

  • EventExecutorEventLoop 在继承体系上有什么异同?为什么 DefaultEventExecutorSingleThreadIoEventLoop 都继承自 SingleThreadEventExecutor,却一个处理业务任务、一个处理 IO 事件?
  • DefaultEventExecutorGroup 的构造链路与 MultiThreadIoEventLoopGroup 共享了哪些模板逻辑?newChild() 工厂方法如何决定创建的是 DefaultEventExecutor 而非 SingleThreadIoEventLoop
  • DefaultEventExecutor.run() 的循环为什么比 SingleThreadIoEventLoop.run() 简单得多?它如何利用 takeTask() 阻塞等待任务,又如何在空闲时优雅退出?
  • pipeline.addLast(EventExecutorGroup, ChannelHandler) 一行代码,如何让该 handler 的所有回调自动切换到业务线程池执行?AbstractChannelHandlerContext.executor() 中的 inEventLoop() 判定又是如何实现线程切换的?

本文将沿着 DefaultEventExecutorGroup 的构造链路和 AbstractChannelHandlerContext 的事件传播路径,逐一分析 EventExecutor 业务线程池的创建、运行、线程切换与优雅关闭机制。

一、继承体系:EventExecutor 与 EventLoop 的关系

1.1 类继承图谱

在深入源码之前,先通过一张继承图谱建立全局认知。EventExecutorEventLoop 共享 SingleThreadEventExecutor 基类,但分化出两条截然不同的路径:一条走向 IO 多路复用,一条走向纯任务执行。

Idea中Diagrams继承图:

从继承图谱可以看出几个关键设计:

  • EventExecutorGroup 接口分化出两条平行分支:EventExecutor(通用执行器接口)和 EventLoopGroup(IO 事件循环组接口)。EventLoop 接口同时继承 OrderedEventExecutorEventLoopGroup,是两条分支的汇合点。
  • SingleThreadEventExecutor 作为公共基类,提供了 taskQueue(任务队列)、thread(执行线程)、状态机、execute() 等核心能力,是所有单线程执行器的统一抽象。GlobalEventExecutor 作为全局单例也继承了 AbstractScheduledEventExecutor 并实现 OrderedEventExecutor,但不经过 SingleThreadEventExecutor
  • SingleThreadEventLoop 下分两条路径:SingleThreadIoEventLoop(IO 专用,实现 IoEventLoop 接口,4.2 推荐的 IO 事件循环实现)和 DefaultEventLoop(无 IO 能力的 EventLoop 实现);DefaultEventExecutor 直接继承 SingleThreadEventExecutor,只有任务执行能力。
  • DefaultEventExecutorGroup 直接继承 MultithreadEventExecutorGroup,而 MultiThreadIoEventLoopGroup 通过 MultithreadEventLoopGroup 间接继承。两者都通过覆写 newChild() 工厂方法来决定创建不同的子实例。

1.2 EventExecutor 接口的核心方法

EventExecutor 接口继承自 EventExecutorGroup + ThreadAwareExecutor。从接口继承链来看,EventLoop 通过 OrderedEventExecutor 间接继承了 EventExecutor,因此 EventExecutorEventLoop 的父接口而非兄弟。它定义了一些便捷方法,让业务代码可以直接在 EventExecutor 实例上创建 PromiseFuture 等异步编程组件:

java 复制代码
io.netty.util.concurrent.EventExecutor

public interface EventExecutor extends EventExecutorGroup, ThreadAwareExecutor {

    // 返回归属的 EventExecutorGroup
    EventExecutorGroup parent();

    // 判定当前线程是否为该 EventExecutor 的执行线程
    boolean inEventLoop(Thread thread);

    default boolean inEventLoop() {
        return inEventLoop(Thread.currentThread());
    }

    // 创建与该 EventExecutor 关联的异步编程组件
    default <V> Promise<V> newPromise() {
        return new DefaultPromise<>(this);
    }

    default <V> Future<V> newSucceededFuture(V result) {
        return new SucceededFuture<>(this, result);
    }

    default <V> Future<V> newFailedFuture(Throwable cause) {
        return new FailedFuture<>(this, cause);
    }
}

EventExecutorGroup 接口则继承自 ScheduledExecutorService + Iterable<EventExecutor>,定义了生命周期管理方法:

java 复制代码
io.netty.util.concurrent.EventExecutorGroup

public interface EventExecutorGroup extends ScheduledExecutorService, Iterable<EventExecutor> {

    // 轮询选择一个 EventExecutor 执行任务
    EventExecutor next();

    // 优雅关闭(带静默期和超时)
    Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit);

    // 返回关闭完成后的 Future
    Future<?> terminationFuture();

    boolean isShuttingDown();
}

AbstractEventExecutorGroup 提供了模板方法实现,将 submit()schedule()invokeAll()execute() 等操作全部委托给 next() 选出的 EventExecutor

java 复制代码
io.netty.util.concurrent.AbstractEventExecutorGroup#submit

public Future<?> submit(Runnable task) {
    return next().submit(task);
}

这套设计将 Group 和 Executor 之间的关系抽象为"委托 + 模板方法":Group 负责管理一组 Executor 的生命周期并提供轮询选择,Executor 负责实际的任务执行。这种分离使得业务代码无需关心底层是单线程还是多线程池。

二、任务执行引擎:SingleThreadEventExecutor 基类分析

SingleThreadEventExecutorDefaultEventExecutorSingleThreadIoEventLoop 的公共基类,承载了单线程任务执行的核心能力。它抽象出了七大状态机、任务队列管理、线程生命周期控制等通用能力,让子类只需关注 run() 方法的具体实现。

2.1 七大状态机

SingleThreadEventExecutorAtomicIntegerFieldUpdater 原子更新 state 字段,管理着七个状态:

下面通过时序图展示从任务提交到状态转换的完整链路:

上面的时序图清晰地展示了四个关键状态转换节点:

  1. 首次 execute() :触发 startThread(),CAS 将 ST_NOT_STARTEDST_STARTED,启动 Worker 线程
  2. shutdownGracefully() :CAS 将 ST_STARTEDST_SHUTTING_DOWN,向队列投递 WAKEUP_TASK 唤醒阻塞线程
  3. confirmShutdown() :Worker 线程在自己的 run() 循环中调用,确认队列已排空且静默期已过,进入 ST_SHUTDOWN
  4. 线程退出 :最终进入 ST_TERMINATEDterminationFuture 被标记为成功

状态流转的源码实现集中在 shutdown0() 方法中:

java 复制代码
io.netty.util.concurrent.SingleThreadEventExecutor#shutdown0

private void shutdown0(long quietPeriod, long timeout, int shutdownState) {
    // 提前返回:如果已经处于关闭状态,无需重复操作
    if (isShuttingDown()) {
        return;
    }

    boolean inEventLoop = inEventLoop();
    boolean wakeup;
    int oldState;
    for (;;) {
        if (isShuttingDown()) {
            return;
        }
        int newState;
        wakeup = true;
        oldState = state;
        if (inEventLoop) {
            // 在 EventLoop 线程内调用,直接切换到目标状态
            newState = shutdownState;
        } else {
            // 在外部线程调用,根据当前状态决定是否允许转换
            switch (oldState) {
                case ST_NOT_STARTED:
                case ST_STARTED:
                case ST_SUSPENDING:
                case ST_SUSPENDED:
                    newState = shutdownState;
                    break;
                default:
                    // 已经处于 ST_SHUTTING_DOWN 或更晚状态,不改变状态,也不唤醒
                    newState = oldState;
                    wakeup = false;
            }
        }
        if (STATE_UPDATER.compareAndSet(this, oldState, newState)) {
            break;
        }
    }
    if (quietPeriod != -1) {
        gracefulShutdownQuietPeriod = quietPeriod;
    }
    if (timeout != -1) {
        gracefulShutdownTimeout = timeout;
    }
    // 确保线程已启动(用于处理 Executor 从未执行过任务就被关闭的场景)
    if (ensureThreadStarted(oldState)) {
        return;
    }
    // 状态从活跃态转换到关闭态且当前不在 EventLoop 线程中时,需要唤醒 Worker 线程
    if (wakeup) {
        taskQueue.offer(WAKEUP_TASK);
        if (!addTaskWakesUp) {
            wakeup(inEventLoop);
        }
    }
}

2.2 taskQueue 任务队列

taskQueueSingleThreadEventExecutor 的核心数据结构,默认使用 LinkedBlockingQueue(默认容量 Integer.MAX_VALUE,实际近似无界)。它的容量由 maxPendingTasks 控制,可通过系统属性 io.netty.eventexecutor.maxPendingTasks 配置:

java 复制代码
io.netty.util.concurrent.SingleThreadEventExecutor

static final int DEFAULT_MAX_PENDING_EXECUTOR_TASKS = Math.max(16,
        SystemPropertyUtil.getInt("io.netty.eventexecutor.maxPendingTasks", Integer.MAX_VALUE));

任务入队通过 addTask() 方法:

java 复制代码
io.netty.util.concurrent.SingleThreadEventExecutor#addTask

protected void addTask(Runnable task) {
    ObjectUtil.checkNotNull(task, "task");
    if (!offerTask(task)) {
        // 队列满,调用 RejectedExecutionHandler 拒绝任务
        reject(task);
    }
}

final boolean offerTask(Runnable task) {
    if (isShutdown()) {
        reject();
    }
    return taskQueue.offer(task);
}

takeTask() 是阻塞取任务的方法,它同时兼顾了 scheduledTaskQueue 中的定时任务:

java 复制代码
io.netty.util.concurrent.SingleThreadEventExecutor#takeTask

protected Runnable takeTask() {
    assert inEventLoop();
    if (!(taskQueue instanceof BlockingQueue)) {
        throw new UnsupportedOperationException();
    }
    BlockingQueue<Runnable> taskQueue = (BlockingQueue<Runnable>) this.taskQueue;
    for (;;) {
        ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
        if (scheduledTask == null) {
            // 无定时任务,永久阻塞等待普通任务
            Runnable task = null;
            try {
                task = taskQueue.take();
                if (task == WAKEUP_TASK) {
                    task = null;
                }
            } catch (InterruptedException e) {
                // Ignore
            }
            return task;
        } else {
            long delayNanos = scheduledTask.delayNanos();
            Runnable task = null;
            if (delayNanos > 0) {
                try {
                    // 有定时任务,以定时任务的延迟时间为超时进行 poll
                    task = taskQueue.poll(delayNanos, TimeUnit.NANOSECONDS);
                } catch (InterruptedException e) {
                    return null;
                }
            }
            if (task == null) {
                // 超时未取到普通任务,从定时任务队列中拉取到期的定时任务
                fetchFromScheduledTaskQueue();
                task = taskQueue.poll();
            }
            if (task != null) {
                if (task == WAKEUP_TASK) {
                    return null;
                }
                return task;
            }
        }
    }
}

takeTask() 的设计体现了"定时任务优先"策略:先检查 scheduledTaskQueue 中是否有到期定时任务,若有则以定时任务的延迟时间为超时 poll(),若无则 take() 永久阻塞。WAKEUP_TASK 是一个哨兵值,用于唤醒阻塞线程但不返回实际任务。

2.3 execute() 与 startThread() 的延迟启动

SingleThreadEventExecutor 采用延迟启动 策略:线程不是在构造时创建,而是在第一次 execute() 提交任务时才启动。这避免了线程资源的浪费。

java 复制代码
io.netty.util.concurrent.SingleThreadEventExecutor#execute

public void execute(Runnable task) {
    execute0(task);
}

private void execute0(Runnable task) {
    ObjectUtil.checkNotNull(task, "task");
    execute(task, wakesUpForTask(task));
}

private void execute(Runnable task, boolean immediate) {
    boolean inEventLoop = inEventLoop();
    addTask(task);         // 先入队任务
    if (!inEventLoop) {
        startThread();     // 非 EventLoop 线程调用时,触发线程启动
        if (isShutdown()) {
            // 已关闭则移除任务并拒绝
            boolean reject = false;
            try {
                if (removeTask(task)) {
                    reject = true;
                }
            } catch (UnsupportedOperationException e) {
                // ignore
            }
            if (reject) {
                reject();
            }
        }
    }
    if (!addTaskWakesUp && immediate) {
        wakeup(inEventLoop);
    }
}

startThread() 通过 CAS 确保只启动一次线程:

java 复制代码
io.netty.util.concurrent.SingleThreadEventExecutor#startThread

private void startThread() {
    int currentState = state;
    // 仅当状态为 ST_NOT_STARTED 或 ST_SUSPENDED 时才启动线程
    if (currentState == ST_NOT_STARTED || currentState == ST_SUSPENDED) {
        if (STATE_UPDATER.compareAndSet(this, currentState, ST_STARTED)) {
            // 重置空闲/繁忙周期计数器,为新线程的运行周期重新开始统计
            resetIdleCycles();
            resetBusyCycles();
            boolean success = false;
            try {
                doStartThread();
                success = true;
            } finally {
                if (!success) {
                    // 启动失败,状态回退
                    STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
                }
            }
        }
    }
}

doStartThread() 通过 executor.execute() 启动新线程,线程启动后立即执行传入的 Runnablerun() 方法:

java 复制代码
io.netty.util.concurrent.SingleThreadEventExecutor#doStartThread

private void doStartThread() {
    executor.execute(new Runnable() {
        @Override
        public void run() {
            processingLock.lock();
            thread = Thread.currentThread();
            if (interrupted) {
                thread.interrupt();
                interrupted = false;
            }
            boolean success = false;
            Throwable unexpectedException = null;
            updateLastExecutionTime();
            boolean suspend = false;
            try {
                for (;;) {
                    SingleThreadEventExecutor.this.run();  // 调用子类的 run()
                    success = true;

                    // ---- 检查是否需要挂起线程 ----
                    int currentState = state;
                    if (canSuspend(currentState)) {
                        if (!STATE_UPDATER.compareAndSet(
                                SingleThreadEventExecutor.this, ST_SUSPENDING, ST_SUSPENDED)) {
                            // CAS 失败,说明有并发操作改变了状态,重试 run() 循环
                            continue;
                        }

                        if (!canSuspend(ST_SUSPENDED) && STATE_UPDATER.compareAndSet(
                                SingleThreadEventExecutor.this, ST_SUSPENDED, ST_STARTED)) {
                            // 挂起期间又有新任务入队,重新激活线程,继续 run() 循环
                            continue;
                        }
                        // 确认挂起:标记 suspend = true,后续 finally 走 suspend 路径
                        suspend = true;
                    }
                    break;
                }
            } catch (Throwable t) {
                unexpectedException = t;
                logger.warn("Unexpected exception from an event executor: ", t);
            } finally {
                // ---- 双路径分支:suspend 路径 vs shutdown 路径 ----
                boolean shutdown = !suspend;
                if (shutdown) {
                    // 【shutdown 路径】确认进入 SHUTTING_DOWN 状态
                    for (;;) {
                        int oldState = state;
                        if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
                                SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
                            break;
                        }
                    }
                    if (success && gracefulShutdownStartTime == 0) {
                        if (logger.isErrorEnabled()) {
                            logger.error("Buggy " + EventExecutor.class.getSimpleName() +
                                    " implementation; " +
                                    SingleThreadEventExecutor.class.getSimpleName() +
                                    ".confirmShutdown() must be called before run() " +
                                    "implementation terminates.");
                        }
                    }
                }

                try {
                    if (shutdown) {
                        // 优雅关闭:排空剩余任务、执行 shutdownHooks
                        for (;;) {
                            if (confirmShutdown()) {
                                break;
                            }
                        }

                        // 从 ST_SHUTTING_DOWN → ST_SHUTDOWN,拒绝新任务提交
                        for (;;) {
                            int currentState = state;
                            if (currentState >= ST_SHUTDOWN || STATE_UPDATER.compareAndSet(
                                    SingleThreadEventExecutor.this, currentState, ST_SHUTDOWN)) {
                                break;
                            }
                        }

                        // 最后一轮排空(此时不再接受新任务)
                        confirmShutdown();
                    }
                } finally {
                    // ---- 公共清理逻辑 ----
                    try {
                        if (shutdown) {
                            // 【shutdown 路径】完整清理
                            try {
                                cleanup();                     // 子类清理(如关闭 IO 资源)
                            } finally {
                                FastThreadLocal.removeAll();    // 移除所有 FastThreadLocal
                                STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
                                threadLock.countDown();         // 通知等待线程:Worker 线程已退出
                                int numUserTasks = drainTasks();// 统计并丢弃残余任务
                                if (numUserTasks > 0 && logger.isWarnEnabled()) {
                                    logger.warn("An event executor terminated with " +
                                            "non-empty task queue (" + numUserTasks + ')');
                                }
                                if (unexpectedException == null) {
                                    terminationFuture.setSuccess(null);
                                } else {
                                    terminationFuture.setFailure(unexpectedException);
                                }
                            }
                        } else {
                            // 【suspend 路径】轻量清理
                            FastThreadLocal.removeAll();    // 移除所有 FastThreadLocal
                            threadProperties = null;        // 重置 threadProperties
                        }
                    } finally {
                        thread = null;
                        // 释放 processingLock,让下一个线程可以接管
                        processingLock.unlock();
                    }
                }
            }
        }
    });
}

doStartThread() 的 finally 块实现了 suspend 与 shutdown 双路径分支 ,这是 SingleThreadEventExecutor 线程生命周期管理的核心:

suspend 路径run() 正常退出后 canSuspend() 返回 true):线程挂起而非销毁,执行轻量清理(FastThreadLocal.removeAll() + 重置 threadProperties),随后释放 processingLock。此时 stateST_SUSPENDED,该 EventExecutor 实例可被新线程接管:当后续有新任务提交时,startThread() 检测到 ST_SUSPENDED 状态,创建新线程并获取 processingLock,重新进入 run() 循环。

shutdown 路径run() 异常退出或 canSuspend() 返回 false):线程即将销毁,执行完整清理流程。首先 CAS 将状态推进到 ST_SHUTTING_DOWN,然后通过 confirmShutdown() 循环排空任务队列并执行 shutdownHooks;接着 CAS 将状态从 ST_SHUTTING_DOWN 推进到 ST_SHUTDOWN(拒绝新任务提交);最后执行 cleanup()(子类清理 IO 资源)、FastThreadLocal.removeAll()(防止类卸载时内存泄漏)、threadLock.countDown()(通知等待线程退出)、drainTasks()(统计残余任务)。最终 CAS 将状态设为 ST_TERMINATEDterminationFuture 被标记为成功。

两条路径的关键区别在于:suspend 路径保留线程池资源以支持后续复用,shutdown 路径彻底销毁线程并释放所有资源。processingLocklock()/unlock() 机制确保同一时刻只有一个线程在操作该 EventExecutor 的内部状态。

这套设计的关键在于"先入队,再启动":即使 startThread() 调用与线程实际启动之间存在时间窗口,任务已经安全地在队列中,线程启动后即可消费。CAS 状态机确保无论有多少个线程同时调用 execute(),只有第一个能成功将状态从 ST_NOT_STARTED 翻转为 ST_STARTED

三、DefaultEventExecutorGroup 的构造:从模板方法到工厂模式

DefaultEventExecutorGroup 的构造链路与 MultiThreadIoEventLoopGroup 共享了 MultithreadEventExecutorGroup 的模板逻辑,唯一的差异在于 newChild() 工厂方法返回的实例不同。

3.1 构造链路

下面通过时序图展示 new DefaultEventExecutorGroup(nThreads) 的完整构造链路:

上面的时序图清晰地展示了构造链路中的两个层次:MultithreadEventExecutorGroup 提供模板逻辑(数组初始化、循环创建、选择器创建),DefaultEventExecutorGroup 仅需覆写 newChild() 一个工厂方法来决定具体创建什么类型的子实例。

DefaultEventExecutorGroup 的构造参数层层传递,最终收敛到 MultithreadEventExecutorGroup 的构造器:

java 复制代码
io.netty.util.concurrent.DefaultEventExecutorGroup

public DefaultEventExecutorGroup(int nThreads) {
    this(nThreads, null);
}

public DefaultEventExecutorGroup(int nThreads, ThreadFactory threadFactory) {
    this(nThreads, threadFactory, SingleThreadEventExecutor.DEFAULT_MAX_PENDING_EXECUTOR_TASKS,
            RejectedExecutionHandlers.reject());
}

public DefaultEventExecutorGroup(int nThreads, ThreadFactory threadFactory, int maxPendingTasks,
                                 RejectedExecutionHandler rejectedHandler) {
    super(nThreads, threadFactory, maxPendingTasks, rejectedHandler);
}

MultithreadEventExecutorGroup 的构造逻辑:

java 复制代码
io.netty.util.concurrent.MultithreadEventExecutorGroup

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                        EventExecutorChooserFactory chooserFactory, Object... args) {
    checkPositive(nThreads, "nThreads");
    if (executor == null) {
        executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
    }
    children = new EventExecutor[nThreads];
    for (int i = 0; i < nThreads; i ++) {
        boolean success = false;
        try {
            children[i] = newChild(executor, args);
            success = true;
        } catch (Exception e) {
            throw new IllegalStateException("failed to create a child event loop", e);
        } finally {
            if (!success) {
                // 创建失败时,关闭已创建的子实例
                for (int j = 0; j < i; j ++) {
                    children[j].shutdownGracefully();
                }
                // ...
            }
        }
    }
    chooser = chooserFactory.newChooser(children);
    // 监听所有子实例的终止
    final FutureListener<Object> terminationListener = future -> {
        if (terminatedChildren.incrementAndGet() == children.length) {
            terminationFuture.setSuccess(null);
        }
    };
    for (EventExecutor e: children) {
        e.terminationFuture().addListener(terminationListener);
    }
}

3.2 newChild() 工厂方法

DefaultEventExecutorGroup.newChild() 是唯一的差异化点:

java 复制代码
io.netty.util.concurrent.DefaultEventExecutorGroup#newChild

@Override
protected EventExecutor newChild(Executor executor, Object... args) throws Exception {
    return new DefaultEventExecutor(this, executor, (Integer) args[0], (RejectedExecutionHandler) args[1]);
}

args 数组的前两个元素分别是 maxPendingTasksRejectedExecutionHandler,由 super(nThreads, threadFactory, maxPendingTasks, rejectedHandler) 调用时传入。

MultiThreadIoEventLoopGroup.newChild() 的对比如下:

维度 DefaultEventExecutorGroup MultiThreadIoEventLoopGroup
newChild 返回值 DefaultEventExecutor SingleThreadIoEventLoop
是否具备 IO 能力 是(实现 IoEventLoop)
IoHandler/IoHandle 有(通过 IoHandlerFactory 创建)
addTaskWakesUp 参数 true false
唤醒机制 依赖 addTask() 入队唤醒 依赖 IoHandler 内部唤醒机制

addTaskWakesUp 参数的区别尤为关键:DefaultEventExecutortrue,因为它的 run() 循环中会 takeTask() 阻塞,新的任务入队时需要唤醒线程;SingleThreadIoEventLoopfalse,因为它的 runIo() 步骤已有独立的唤醒机制。

四、DefaultEventExecutor.run() 循环:纯任务执行模式

SingleThreadIoEventLoop.run() 的"IO 处理---批量执行任务"循环不同,DefaultEventExecutor.run() 简化为"取任务→执行→检查退出"三步循环,没有任何 IO 多路复用步骤。

4.1 run() 循环源码

DefaultEventExecutor.run() 的源码极其精简:

java 复制代码
io.netty.util.concurrent.DefaultEventExecutor#run

@Override
protected void run() {
    for (;;) {
        Runnable task = takeTask();       // 阻塞等待任务
        if (task != null) {
            runTask(task);                // 执行任务
            updateLastExecutionTime();    // 更新最后执行时间
        }
        if (confirmShutdown()) {          // 检查是否需要退出
            break;
        }
    }
}

SingleThreadIoEventLoop.run() 的对比鲜明:

步骤 SingleThreadIoEventLoop.run() DefaultEventExecutor.run()
1. IO 初始化 ioHandler.initialize()
2. IO 处理 runIo()
3. 任务执行 runAllTasks(maxTaskProcessingQuantumNs) takeTask() + runTask()
4. 退出检查 confirmShutdown() + canSuspend() confirmShutdown()

DefaultEventExecutor 不需要 IoHandler、不需要 runIo(),它唯一的职责就是"从队列中取出任务,执行它"。

4.2 takeTask() 阻塞等待与 runAllTasks() 批量排空

takeTask() 在 2.2 节已经分析过,这里补充 runTask()runAllTasks() 的实现:

java 复制代码
io.netty.util.concurrent.SingleThreadEventExecutor#runAllTasks

protected boolean runAllTasks() {
    assert inEventLoop();
    boolean fetchedAll;
    boolean ranAtLeastOne = false;
    do {
        // 先将定时任务队列中的到期任务转移到 taskQueue
        fetchedAll = fetchFromScheduledTaskQueue(taskQueue);
        if (runAllTasksFrom(taskQueue)) {
            ranAtLeastOne = true;
        }
    } while (!fetchedAll); // 持续直到所有定时任务都被转移完毕
    if (ranAtLeastOne) {
        lastExecutionTime = getCurrentTimeNanos();
    }
    afterRunningAllTasks();
    return ranAtLeastOne;
}
java 复制代码
io.netty.util.concurrent.SingleThreadEventExecutor#runAllTasksFrom

protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) {
    Runnable task = pollTaskFrom(taskQueue);
    if (task == null) {
        return false;
    }
    for (;;) {
        safeExecute(task);                    // 安全执行(捕获异常)
        task = pollTaskFrom(taskQueue);       // 非阻塞 poll
        if (task == null) {
            return true;
        }
    }
}

runAllTasks()confirmShutdown() 中被调用,用于排空任务队列中的所有剩余任务。它与 run() 循环中的 takeTask() 形成互补:takeTask() 是阻塞等待(有任务则执行,无任务则等待),runAllTasks() 是批量排空(一口气执行完所有任务)。

4.3 confirmShutdown() 优雅退出

confirmShutdown() 是优雅关闭的核心,它利用"静默期"(quiet period)机制确保在关闭前所有任务都有机会被执行:

java 复制代码
io.netty.util.concurrent.SingleThreadEventExecutor#confirmShutdown

protected boolean confirmShutdown() {
    if (!isShuttingDown()) {
        return false;                           // 尚未开始关闭,继续循环
    }
    if (!inEventLoop()) {
        throw new IllegalStateException("must be invoked from an event loop");
    }
    cancelScheduledTasks();                     // 取消所有定时任务
    if (gracefulShutdownStartTime == 0) {
        gracefulShutdownStartTime = getCurrentTimeNanos();
    }
    if (runAllTasks() || runShutdownHooks()) {
        if (isShutdown()) {
            return true;                        // 已进入 ST_SHUTDOWN,确认退出
        }
        if (gracefulShutdownQuietPeriod == 0) {
            return true;                        // 静默期为 0,立即退出
        }
        taskQueue.offer(WAKEUP_TASK);           // 重新入队哨兵,等待下一轮检查
        return false;
    }
    final long nanoTime = getCurrentTimeNanos();
    if (isShutdown() || nanoTime - gracefulShutdownStartTime > gracefulShutdownTimeout) {
        return true;                            // 超时,强制退出
    }
    if (nanoTime - lastExecutionTime <= gracefulShutdownQuietPeriod) {
        // 静默期内仍有任务执行,等待 100ms 后再检查
        taskQueue.offer(WAKEUP_TASK);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // Ignore
        }
        return false;
    }
    // 静默期内无新任务提交,确认退出
    return true;
}

优雅关闭的"静默期"机制:在 shutdownGracefully() 被调用后,confirmShutdown() 会持续检查 lastExecutionTime(最后一次任务执行时间)。如果在 gracefulShutdownQuietPeriod 时间内没有任何新任务执行,说明队列已空闲,可以安全关闭。如果超过 gracefulShutdownTimeout 仍未空闲,则强制退出。这套机制确保"该做的任务都做完,但不会无限等待"。

五、Pipeline 线程切换:IO 线程与业务线程的隔离

DefaultEventExecutor 本身只是一个"能执行任务的单线程",它如何与 ChannelPipeline 配合,实现 IO 线程与业务线程的隔离?答案在于 AbstractChannelHandlerContextexecutor() 判定机制。

5.1 pipeline.addLast(EventExecutorGroup, ChannelHandler) 的绑定

当用户调用 pipeline.addLast(group, handler) 时,DefaultEventExecutorGroup 被绑定到特定的 ChannelHandler 上:

newContext() 方法中调用 childExecutor(group) 从 Group 中轮询选择一个 EventExecutor

java 复制代码
io.netty.channel.DefaultChannelPipeline#newContext

private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
    return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}

private EventExecutor childExecutor(EventExecutorGroup group) {
    if (group == null) {
        return null;
    }
    // 检查 SINGLE_EVENTEXECUTOR_PER_GROUP 选项
    // 当该选项设为 false 时,每个 handler 绑定通过 group.next() 获取不同的 EventExecutor,不执行缓存
    Boolean pinEventExecutor = channel().config().getOption(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP);
    if (pinEventExecutor != null && !pinEventExecutor) {
        return group.next();       // 不缓存,每次调用 group.next() 获取不同的 EventExecutor
    }
    // 使用 childExecutors 缓存,确保同一个 Group 的同一个 Channel 复用同一个 EventExecutor
    Map<EventExecutorGroup, EventExecutor> childExecutors = this.childExecutors;
    if (childExecutors == null) {
        childExecutors = this.childExecutors = new IdentityHashMap<>(4);
    }
    EventExecutor childExecutor = childExecutors.get(group);
    if (childExecutor == null) {
        childExecutor = group.next();
        childExecutors.put(group, childExecutor);
    }
    return childExecutor;
}

AbstractChannelHandlerContext 的构造器保存 childExecutor 并判定 ordered

java 复制代码
io.netty.channel.AbstractChannelHandlerContext

final EventExecutor childExecutor;

AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,
                              String name, Class<? extends ChannelHandler> handlerClass) {
    this.name = ObjectUtil.checkNotNull(name, "name");
    this.pipeline = pipeline;
    childExecutor = executor;
    executionMask = mask(handlerClass);
    // 如果 executor 为 null(未绑定)或 executor 实现了 OrderedEventExecutor,则执行是有序的
    ordered = executor == null || executor instanceof OrderedEventExecutor;
}

childExecutor 的缓存机制与 SINGLE_EVENTEXECUTOR_PER_GROUP 选项值得注意:DefaultChannelPipeline 使用 IdentityHashMap<EventExecutorGroup, EventExecutor> 缓存同一个 Group 在同一个 Channel 上分配的 EventExecutor,这意味着如果同一个 Group 被用于绑定多个 handler,这些 handler 将共享同一个 EventExecutor 实例,保证任务执行顺序。但这一行为可以通过 ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP 选项控制:当该选项设为 false 时,childExecutor() 直接返回 group.next() 而不缓存,使得每个 handler 可以绑定到不同的 EventExecutor,实现更大的并行度。

5.2 executor() 的懒加载与缓存

executor() 是热路径方法,每次事件传播时都会调用。它的实现采用了懒加载 + 缓存策略:

java 复制代码
io.netty.channel.AbstractChannelHandlerContext#executor

// 缓存 executor() 的计算结果,热路径优化
EventExecutor contextExecutor;

public EventExecutor executor() {
    EventExecutor ex = contextExecutor;
    if (ex == null) {
        // 懒加载:childExecutor 不为 null 则使用业务线程池,否则使用 IO 线程
        contextExecutor = ex = childExecutor != null ? childExecutor : channel().eventLoop();
    }
    return ex;
}

如果 childExecutor 不为 null(即 handler 绑定了 EventExecutorGroup),则 executor() 返回业务线程池中的 EventExecutor;否则返回 channel().eventLoop()(即 IO 线程)。contextExecutor 缓存避免每次调用都重新判断,这是一个典型的"热路径 micro-optimization"。

5.3 fireChannelRead() 的线程切换

当事件在 Pipeline 中传播时,AbstractChannelHandlerContext 的每一个 fireXxx() 方法都会先检查目标 handler 的 executor() 是否与当前线程一致:

java 复制代码
io.netty.channel.AbstractChannelHandlerContext#fireChannelRead

public ChannelHandlerContext fireChannelRead(final Object msg) {
    AbstractChannelHandlerContext next = findContextInbound(MASK_CHANNEL_READ);
    if (next.executor().inEventLoop()) {
        // 当前线程就是目标 handler 的执行线程,直接执行
        final Object m = pipeline.touch(msg, next);
        if (next.invokeHandler()) {
            try {
                final ChannelHandler handler = next.handler();
                final DefaultChannelPipeline.HeadContext headContext = pipeline.head;
                if (handler == headContext) {
                    headContext.channelRead(next, m);
                } else if (handler instanceof ChannelDuplexHandler) {
                    ((ChannelDuplexHandler) handler).channelRead(next, m);
                } else {
                    ((ChannelInboundHandler) handler).channelRead(next, m);
                }
            } catch (Throwable t) {
                next.invokeExceptionCaught(t);
            }
        } else {
            next.fireChannelRead(m);
        }
    } else {
        // 当前线程不是目标 handler 的执行线程,提交到业务线程池异步执行
        next.executor().execute(() -> fireChannelRead(msg));
    }
    return this;
}

这套线程切换模板适用于所有 inbound 事件方法:fireChannelRegisteredfireChannelActivefireChannelInactivefireChannelReadCompletefireExceptionCaughtfireUserEventTriggered 等均遵循相同的 inEventLoop() 判定 + execute() 异步提交模式。

fireChannelRead 为例,线程切换的完整链路如下:

  1. IO 线程在 SingleThreadIoEventLoop.run() 中调用 pipeline.fireChannelRead(msg),事件从 Head Context 开始传播
  2. 当遍历到绑定了 EventExecutor 的 handler 时,next.executor() 返回业务 EventExecutor
  3. next.executor().inEventLoop() 返回 false(当前线程是 IO 线程,不是业务线程)
  4. next.executor().execute(() -> fireChannelRead(msg)) 将任务提交到业务线程池的 taskQueue
  5. 业务线程的 DefaultEventExecutor.run() 循环中,takeTask() 取出该任务并执行
  6. 业务线程中继续向下传播事件,后续 handler 的 executor() 可能返回同一个业务 EventExecutorinEventLoop() 返回 true),直接在业务线程中执行

六、整体链路串联:从任务提交到优雅关闭

将前五节的内容串联起来,一条完整的 EventExecutor 业务线程池工作链路如下:

核心设计思想可以凝练为三个关键词:

  • "模板方法 + 工厂模式":MultithreadEventExecutorGroup 提供构造模板,newChild() 工厂方法决定子类型,实现 Group 与 Executor 的解耦
  • "inEventLoop() 判定 + execute() 异步提交":AbstractChannelHandlerContext 在事件传播时自动判定线程归属,将耗时任务从 IO 线程卸载到业务线程池
  • "状态机 + 静默期":七大状态管理线程生命周期,静默期机制确保优雅关闭时"该做的任务都做完,但不会无限等待"

七、全文小结

本文聚焦 EventExecutor 业务线程池,从继承体系与任务执行引擎两个维度,深入分析了 Netty 如何通过 DefaultEventExecutorGroupDefaultEventExecutor 实现 IO 线程与业务线程的隔离,防止耗时任务阻塞 IO 事件处理。

在继承体系方面,EventExecutorEventLoop 共享 SingleThreadEventExecutor 基类,差异在于 SingleThreadIoEventLoop 额外实现了 IoEventLoop 接口具备 IO 多路复用能力,而 DefaultEventExecutor 仅专注于任务执行。SingleThreadEventExecutor 通过七大状态机管理线程生命周期,taskQueueLinkedBlockingQueue)作为任务缓冲区,takeTask() 阻塞等待任务同时兼顾定时任务调度。在构造链路方面,DefaultEventExecutorGroup 继承 MultithreadEventExecutorGroup,通过 newChild() 工厂方法创建 DefaultEventExecutor,复用了 MultiThreadIoEventLoopGroup 的构造模板逻辑。在运行机制方面,DefaultEventExecutor.run() 简化为"取任务→执行→检查退出"三步循环,没有 IO 多路复用步骤,confirmShutdown() 通过静默期机制实现优雅关闭。在线程切换方面,pipeline.addLast(EventExecutorGroup, handler) 为 handler 绑定专用 EventExecutorAbstractChannelHandlerContext 在事件传播时通过 executor().inEventLoop() 判定是否需要线程切换,所有 inbound 事件方法均遵循同一套切换模板。


原创不易,如果本文对您有帮助,带来了些许灵感或启发,烦请动动小手点赞、关注、转发、收藏。这是作者持续更新的动力源泉,衷心感谢您的支持。我会尽量在工作之余,为大家带来更高品质的内容,努力保持周更。

相关推荐
吴声子夜歌18 分钟前
Java——基本类型
java·开发语言
柠檬味拥抱22 分钟前
鸿蒙 AI 对话实战:用蓝耘 MaaS 给礼物 App 装上「多轮对话选礼大脑」
后端
唐青枫28 分钟前
别再把 build.zig 当配置文件:Zig 构建系统从零到实战
后端
Naylor30 分钟前
只借不占:Rust 的引用与借用
后端·rust
大勇前进38 分钟前
Python装饰器、生成器、上下文管理器:3个必会的"魔法"机制
后端
Go_error1 小时前
Fyne:让 Go 开发者也能玩转 GUI
后端·go
用户0795675043141 小时前
Python + OpenCV 识别 GIF 中旋转最快的图形:基于时间周期的动态识别
后端
长大19881 小时前
Python+Flask 1小时搭建个人博客:比WordPress轻量10倍
后端
闪学it1 小时前
Python测试开发进阶线上班28期
后端