Netty源码分析---inEventLoop方法详解

第二章Netty,EventLoop线程切换源码分析 摘要:本文深入解析Netty的线程切换机制,揭示其基于单线程事件循环的核心设计原理。通过NioEventLoop主循环源码分析,展示其如何处理IO事件和队列任务。重点剖析Handler线程自动切换的实现机制,包括跨线程任务提交的底层逻辑,最终给出线程隔离最佳实践:IO线程仅处理轻量操作,耗时业务交由独立线程池,并通过事件循环机制保证线程安全。全文从源码层面系统阐释了Netty实现高性能线程调度的关键技术。 阅读详情

相关文章链接

位运算详解

waken方法详解

ThreadPerTaskExecutor与线程创建详解

processSelectedKeys() vs runAllTasks()

NioServerSocketChannel-Unsafe初始化详解

NioEventLoop的run方法详解

NioEventLoopGroup深度解析

inEventLoop方法详解

executionMask详解

Netty源码分析–认真系列(一)

Netty源码分析–认真系列(二)

inEventLoop() 方法详解

问题代码

@Override
public void execute(Runnable task) {
    // 1. 判断当前线程是否是 EventLoop 的线程
    boolean inEventLoop = inEventLoop();
    
    // 2. 将任务添加到任务队列
    addTask(task);
    
    // 3. 如果不是 EventLoop 线程提交的任务
    if (!inEventLoop) {
        // 启动 EventLoop 的线程(如果还没启动)
        startThread();
    }
}

你的疑问:inEventLoop() 的作用是什么?


核心答案

inEventLoop() 用于判断"当前正在执行代码的线程"是否就是"这个 EventLoop 内部的线程"。

简单理解

// 假设 NioEventLoop 内部有一个线程,叫 "nioEventLoop-1-1"
// 现在有人调用了 eventLoop.execute(task)

// 情况1:main 线程调用
Thread.currentThread().getName();  // "main"
eventLoop.thread.getName();        // "nioEventLoop-1-1"
inEventLoop();                     // false(不是同一个线程)

// 情况2:EventLoop 自己的线程调用
Thread.currentThread().getName();  // "nioEventLoop-1-1"
eventLoop.thread.getName();        // "nioEventLoop-1-1"
inEventLoop();                     // true(是同一个线程)

为什么需要这个判断?

原因 1:避免不必要的线程切换

@Override
public void execute(Runnable task) {
    boolean inEventLoop = inEventLoop();
    addTask(task);
    
    if (!inEventLoop) {
        // 只有在外部线程调用时,才需要启动 EventLoop 线程
        startThread();
    }
    // 如果已经在 EventLoop 线程中,就不需要做任何额外操作
    // 因为 EventLoop 的 run() 方法会自己从 taskQueue 中取任务执行
}

场景对比:

// 场景 A:main 线程提交任务
main 线程 -> eventLoop.execute(task)
    -> inEventLoop() = false
    -> startThread()  // 需要启动或唤醒 EventLoop 线程
    -> EventLoop 线程从 taskQueue 取任务执行

// 场景 B:EventLoop 线程自己提交任务
EventLoop 线程 -> eventLoop.execute(task)
    -> inEventLoop() = true
    -> 不需要 startThread()  // 因为自己就在运行中
    -> 继续执行 run() 方法,自然会处理 taskQueue 中的任务

原因 2:优化性能

// 如果不判断,每次都调用 startThread()
if (!inEventLoop) {
    startThread();  // 这个方法内部有 CAS 操作,有性能开销
}

// 如果已经在 EventLoop 线程中,完全不需要这个开销

原因 3:避免死锁或递归问题

// 假设没有这个判断,可能出现问题:
EventLoop 线程正在执行任务 A
    -> 任务 A 中又调用 eventLoop.execute(任务 B)
    -> 如果每次都 startThread(),可能导致问题
    -> 但有了判断,就知道"我自己在运行",不需要额外操作

源码实现

inEventLoop() 方法

// AbstractEventExecutor
@Override
public boolean inEventLoop() {
    return inEventLoop(Thread.currentThread());
}

@Override
public boolean inEventLoop(Thread thread) {
    // 比较当前线程和 EventLoop 的线程是否是同一个
    return thread == this.thread;
}

关键点:

  • Thread.currentThread():获取当前正在执行代码的线程
  • this.thread:EventLoop 内部的线程(第一次提交任务时创建)
  • 用 == 比较引用是否相同

实际场景分析

场景 1:服务端启动(main 线程)

// NettyServer.java - main 方法
public static void main(String[] args) {
    EventLoopGroup bossGroup = new NioEventLoopGroup();
    ServerBootstrap b = new ServerBootstrap();
    
    // main 线程执行 bind
    ChannelFuture f = b.bind(port).sync();
}

// 执行流程
main 线程
    |
    |--bind(port)
    |
    |--initAndRegister()
    |
    |--eventLoop.register(channel)
    |
    |--eventLoop.execute(register0 任务)
    |    |
    |    |--inEventLoop()  // 返回 false(main != EventLoop 线程)
    |    |
    |    |--addTask(register0)  // 添加到任务队列
    |    |
    |    |--startThread()  // 启动 EventLoop 线程
    |
    |--返回 Future

场景 2:EventLoop 线程内部提交任务

// 假设在某个 Handler 中
public class MyHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 当前线程:EventLoop 线程
        
        // 提交一个新任务
        ctx.channel().eventLoop().execute(() -> {
            System.out.println("延迟执行的任务");
        });
        
        // 这时 inEventLoop() 返回 true
        // 不需要 startThread(),因为当前线程就是 EventLoop 线程
    }
}

// 执行流程
EventLoop 线程(正在执行 channelRead)
    |
    |--channelRead()
    |
    |--eventLoop.execute(新任务)
    |    |
    |    |--inEventLoop()  // 返回 true(当前线程 == EventLoop 线程)
    |    |
    |    |--addTask(新任务)  // 只添加到队列
    |    |
    |    |--不调用 startThread()  // 因为自己已经在运行
    |
    |--继续执行后续代码
    |
    |--回到 run() 方法的循环
    |
    |--runAllTasks()  // 处理队列中的任务(包括刚才添加的)

场景 3:业务线程提交任务

// 假设有一个业务线程池
ExecutorService businessPool = Executors.newFixedThreadPool(10);

businessPool.submit(() -> {
    // 当前线程:业务线程池的线程
    
    // 向 Channel 写数据
    channel.writeAndFlush(msg);
    
    // 内部会调用 eventLoop.execute()
    // 这时 inEventLoop() 返回 false
    // 需要确保 EventLoop 线程在运行
});

// 执行流程
业务线程
    |
    |--channel.writeAndFlush(msg)
    |
    |--pipeline.write(msg)
    |
    |--最终到 head.write()
    |
    |--unsafe.write()
    |
    |--eventLoop.execute(write 任务)
    |    |
    |    |--inEventLoop()  // 返回 false(业务线程 != EventLoop 线程)
    |    |
    |    |--addTask(write 任务)
    |    |
    |    |--startThread()  // 确保 EventLoop 线程在运行
    |
    |--返回

完整的 execute() 方法逻辑

@Override
public void execute(Runnable task) {
    if (task == null) {
        throw new NullPointerException("task");
    }
    
    // ===== 关键判断 =====
    boolean inEventLoop = inEventLoop();
    
    // 无论如何,先把任务加入队列
    addTask(task);
    
    // ===== 根据判断结果采取不同策略 =====
    if (!inEventLoop) {
        // 情况1:外部线程提交任务
        // 需要确保 EventLoop 线程已启动
        startThread();
        
        // 如果 EventLoop 正在关闭,移除任务并拒绝
        if (isShutdown() && removeTask(task)) {
            reject();
        }
    }
    // 情况2:EventLoop 线程自己提交任务
    // 什么都不做,因为 run() 方法会自己处理 taskQueue
    
    // 唤醒机制(用于某些特殊情况)
    if (!addTaskWakesUp && wakesUpForTask(task)) {
        wakeup(inEventLoop);
    }
}

图解对比

情况 A:外部线程调用(inEventLoop = false)

main 线程                          EventLoop 线程
   |                                    |
   |--execute(task)                     |
   |                                    |
   |--inEventLoop() = false             |
   |                                    |
   |--addTask(task)                     |
   |   [taskQueue: task]                |
   |                                    |
   |--startThread()                     |
   |                                    |
   |                                    |--Thread 启动
   |                                    |
   |                                    |--run() 循环
   |                                    |
   |                                    |--runAllTasks()
   |                                    |
   |                                    |--执行 task
   |                                    |
   |--返回                               |

情况 B:EventLoop 线程自己调用(inEventLoop = true)

EventLoop 线程
   |
   |--正在执行某个任务
   |
   |--execute(newTask)
   |
   |--inEventLoop() = true
   |
   |--addTask(newTask)
   |   [taskQueue: newTask]
   |
   |--不调用 startThread()
   |
   |--继续执行当前任务
   |
   |--当前任务完成
   |
   |--回到 run() 循环
   |
   |--runAllTasks()
   |
   |--执行 newTask

为什么这样设计?

1. 性能优化

// 避免不必要的 CAS 操作
if (!inEventLoop) {
    startThread();  // 内部有 CAS,有开销
}

2. 逻辑清晰

// 明确区分两种情况:
// - 外部调用:需要启动/唤醒线程
// - 内部调用:不需要额外操作

3. 避免问题

// 如果不判断,可能出现:
// - 重复启动线程
// - 不必要的唤醒操作
// - 性能损耗

类比理解

比喻 1:餐厅服务员

服务员(EventLoop 线程)有一个任务清单(taskQueue)

情况 A:顾客(main 线程)叫服务员
    顾客:"服务员,帮我拿杯水"
    服务员可能在休息,需要叫醒他
    -> inEventLoop() = false
    -> startThread()(叫醒服务员)

情况 B:服务员自己给自己加任务
    服务员:"我等会要去收拾桌子"
    服务员自己就在工作,不需要别人叫醒
    -> inEventLoop() = true
    -> 不需要 startThread()

比喻 2:快递员

快递员(EventLoop 线程)有一个派件清单(taskQueue)

情况 A:客户(外部线程)下单
    客户下单 -> 订单进入清单
    需要通知快递员有新订单
    -> inEventLoop() = false
    -> startThread()(通知快递员)

情况 B:快递员自己加任务
    快递员:"送完这单,顺便去取下一批货"
    快递员自己在工作,自己会看清单
    -> inEventLoop() = true
    -> 不需要额外通知

总结

inEventLoop() 的作用

  1. 判断当前线程是否是 EventLoop 的线程
  2. 决定是否需要启动/唤醒 EventLoop 线程
  3. 优化性能,避免不必要的操作

核心逻辑

if (当前线程 == EventLoop 线程) {
    // 只加入队列,不做其他操作
    // 因为 EventLoop 的 run() 方法会自己处理队列
} else {
    // 加入队列 + 确保 EventLoop 线程在运行
    // 因为外部线程不知道 EventLoop 的状态
}
IDEA中Maven工具栏与命令执行全攻略:高效开发必备技巧 IntelliJ IDEA 作为强大的 Java 开发工具,其 Maven 功能独具优势。Maven 工具栏位于工作区右侧,图标功能丰富,涵盖项目导入、源码处理、依赖操作等。执行 Maven 命令有多种途径,Run Anything 窗口可输入或选目标执行,上下文菜单能右键运行目标,还可创建运行配置,指定命令参数后运行,为开发者高效管理 Maven 项目、便捷执行命令提供有力支持。 阅读详情

相关推荐

Oracle VM VirtualBox虚拟机网络配置与静态IP设置实战

本文详细讲解了Oracle VM VirtualBox虚拟机的网络配置,特别是如何设置静态IP以实现稳定访问。文章对比了NAT、桥接、仅主机等网络模式,并提供了从VirtualBox管理界面到Linux系统内部的完整配置步骤,帮助用户解决IP变动问题,构建可靠的本地开发测试环境。

weixin_28365523的博客 511

Netty源码分析---SingleThreadEventExecutor-execute方法详解

本文详细分析了Netty中SingleThreadEventExecutor.execute()方法的实现原理。该方法作为Netty线程模型的核心,主要功能包括:接收用户提交的任务、将任务添加到任务队列、启动EventLoop线程(如未启动)以及必要时唤醒阻塞线程。文章通过源码解析展示了关键步骤:参数校验、线程判断、任务入队和线程启动机制,特别解释了inEventLoop()判断的重要性及MPSC无界队列的特性。该方法实现了多线程到单线程的任务调度转换,确保任务顺序执行,是Netty高性能事件处理的基础。

java_rutu的博客 514

从USB 2.0到USB 3.x:Synopsys SVT USB VIP配置避坑与接口选择指南

本文详细解析了从USB 2.0到USB 3.x的Synopsys SVT USB VIP配置要点,涵盖协议版本选择、物理接口匹配及UVM层次化架构实现。重点介绍了layering sequence的配置技巧和常见问题解决方案,帮助验证工程师高效避坑并优化验证流程。

weixin_42653691的博客 239

Netty源码分析--Channel注册(中)(六)

接上一篇,我们继续看 不知道大家第一次看这段代码的时候有没有一脸懵逼,反正我是一脸懵,为什么这个if else 最终都是调用的register0方法,都是一样的。 其实这里就是为什么Netty是线程安全的根本原因。 我们先看下eventLoop.inEventLoop() 方法 第一张图传入了 当前的 线程, 第二...

weixin_30952103的博客 138

Memory SystemsCache, DRAM, Disk翻译学习DRAM部分(二)

DRAM 的发展史

qq_33473931的博客 996

ODBC 连接数据库 SQL server 2008

1、打开【控制面板】-> 【系统和安全】 -> 【管理工具】-> 【数据源(ODBC)】  2、双击【数据源(ODBC)】-> 选中【系统DSN】 3、单击【添加】,会弹出如下对话框,选中【SQL server】->【完成】 4、弹出如下对话框,在【名称】中填入数据源名称 例如:我命名为: LocalServer,在【服务器】中填入计算机名或者IP地址,我连接

Christine的专栏 8518

Netty入门 初识Netty helloword netty组件EventLoop源码分析

1.概述 Netty是一个异步的、基于事件驱动(Selector)的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端。 Netty: Home Netty的作者为韩国的Trustin lee Netty在java网络应用框架中的地位就好比:spring在javaee开发中的地位 以下的框架都使用到了Netty因为他们都有网络通信需求: Cassandra - nosql数据库 Spark - 大数据分布式计算框架 Hadoop - 大数据分布式存储框架 RocketMQ -

清风 764

DeepSeek+Coze扣子搭建智能体(保姆级教程)

DeepSeek+Coze扣子搭建智能体(保姆级教程)

yizhen91y的博客 1万+

Netty源码分析(五):EventLoop

上一篇主要介绍了一下EventLoopGroup,本篇详细看下它的成员EventLoop。 类结构 NioEventLoop继承自SingleThreadEventLoop,而SingleThreadEventLoop又继承自SingleThreadEventExecutor。 SingleThreadEventExecutor内部持有一个Thread对象,是Netty多线程的基础。 可以认...

weixin_33836223的博客 105

Cloudflare SaaS 优选IP实战:提升大陆访问速度的配置指南

本文详细介绍了如何通过Cloudflare SaaS优选IP提升中国大陆用户访问速度的配置指南。从基础环境准备到IP筛选方法,再到完整配置流程和效果验证,帮助用户突破免费套餐限制,精准优化网络线路,特别适合跨境电商和面向中国大陆用户的网站优化。

weixin_29231027的博客 686

【SMPL简介】SMPL: A Skinned Multi-Person Linear Model【源码复现】

SMPL是一种3D人体建模方法.在数字人或者人物角色三维重建领域有着广泛应用支持人体的各种形状及动作可以简单理解为通过训练获取的人物模型SMPL 输入是:姿态shape vectorβ\betaβ+ 影响动作Pose的参数θ\thetaθ输出是: 各种变化后vertex顶点的数据作者:Loper, Matthew and Mahmood, Naureen and Romero, Javier and Pons-Moll, Gerard and Black, Michael J.

zik的博客 3369

Netty源码分析之三【我就是大名鼎鼎的 EventLoop】

简述这一章是 Netty 源码分析 的第三章, 我将在这一章中大家一起探究一下 Netty 的 EventLoop 的底层原理, 让大家对 Netty 的线程模型有更加深入的了解.NioEventLoopGroup在[Netty 源码分析之 一 揭开 Bootstrap 神秘的红盖头]章节中我们已经知道了, 一个 Netty 程序启动时, 至少要指定一个 EventLoopGroup(如果使用到的是

极客神殿 1039

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Netty源码分析6:NioEventLoop

NioEventLoop基本介绍 在构建Netty服务时,首先需要创建一个NioEventLoopGroup,即如下代码: ··· NioEventLoopGroup boss = new NioEventLoopGroup(1); NioEventLoopGroup worker = new NioEventLoopGroup(); ··· 初始化时机:NioEventLoopGroup内部定义了一个EventExecutor[] children成员变量(NioEventLoop是EventExec

heng_zou的博客 510

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop(二)

接上篇Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop(一) Netty 的 IO 处理循环 在 Netty 中, 一个 EventLoop 需要负责两个工作, 第一个是作为 IO 线程, 负责相应的 IO 操作; 第二个是作为任务线程, 执行 taskQueue 中的任务. 接下来我们先从 IO 操纵方面入手, 看一下 ...

weixin_34144848的博客 194

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Netty 源码学习——EventLoop

Netty 源码学习——EventLoop 在前面 Netty 源码学习——客户端流程分析中我们已经知道了一个 EventLoop 大概的流程,这一章我们来详细的看一看。 NioEventLoopGroup 类层次结构 我们先来看下 NioEventLoopGroup 这个类。 public class NioEventLoopGroup extends MultithreadEventLo...

qq_16830879的博客 322

自顶向下深入分析Netty(七)--ChannelPipeline源码实现

7.2 源码分析 7.2.1 ChannelPipeline 首先看ChannelPipeline接口的关键方法,相似方法只列出一个: ChannelPipeline addLast(String name, ChannelHandler handler); ChannelPipeline remove(ChannelHandler handler); Cha

Fashion-liu的博客 593

ChannelHandlerContext 常用Api源码解读

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33787529的博客 353

InnoDB 内存结构总览:Change Buffer、AHI 与 Log Buffer

本文系统梳理InnoDB内存结构中的ChangeBuffer、Adaptive Hash Index(AHI)与LogBuffer。ChangeBuffer缓存二级索引页不在内存时的变更,减少随机IO,仅在页加载时合并;聚簇索引修改立即执行,因数据本身不可延后。AHI基于高频等值查询自动构建哈希索引,加速定位,仅支持等值操作。LogBuffer为redo log内存缓冲,配合磁盘日志实现崩溃恢复。三者均服务于性能优化,其中ChangeBuffer与AHI共享BufferPool空间,而LogBuffer独立

bksczm的博客 298

信创环境下PageOffice文档控件适配与实践

信创适配做到文档控件这一层,拼的已经不是"有没有国产版本",而是同一个功能在两类终端上是否真的等价。等价的标准很具体:同一次点击能得到同样的结果,同一份公文能得到同样的版式,同一个项目不需要维护两套代码。这背后是三件并不轻松的工作:服务端用一套接口同时支撑两类终端,客户端把两种系统与四种芯片的安装包分发清楚,文档内核层由产品侧跟着 WPS 的版本走。浏览器这一层的压力,则被收进了产品自己的窗口里——升级节奏由产品掌握,不再交给运气。

zi_wu_xian的专栏 327

第三篇 第 6 课

前置:第三篇第 1 课(stdx 引入),第二篇第 1 课(std.regex)。环境:仓颉 SDK 1.2.0。本文所有代码与输出均逐行实测。

weixin_41618057的博客 516

V8 机制、手写核心函数与 TypeScript 类型体操

或固定初始化(如数组先定长、对象先给全字段)保持类型与形状稳定;这些都比"换框架"更基础。只能处理纯数据(丢函数、Date、RegExp、循环引用报错)。生产级深拷贝要处理:原始值短路、循环引用、V8(Chrome / Node 的 JS 引擎)不是"解释一行跑一行"那么简单,它用。图:V8 执行流程——解释执行 + 热点优化,优化失败会 Deopt 回退。JS 内存自动管理,但"自动"不等于"不会漏"。是"固定间隔最多执行一次"。手写 Promise 是"懂异步"的试金石。是"停下后才执行最后一次",

qq_38468358的博客 351

【从零理解 Spring AI · 12】MCP 的两种角色:做客户端还是做服务端

上一篇讲了 MCP 的概念:标准协议、三大角色、M+N 替代 M×N。

nice_lcj520的博客 538

Java 集合容器高频面试题全解:HashMap 树化阈值、1.7 死循环与 ConcurrentHashMap 演进(2026 招聘 JD 真题复盘)

HashMap 树化阈值 8 的泊松分布依据、退化阈值 6 的防抖设计、JDK 1.7 头插成环死循环的排查与修复、1.8 尾插后为何仍线程不安全、ConcurrentHashMap 放弃分段锁的原因与 ForwardingNode 协作扩容、computeIfAbsent 死锁陷阱、大集合找重复的场景题选型,附三层追问链自测。题目来源为 38 期公开招聘 JD 样本与字节、拼多多、建行、美团、快手公开面经。

蒙眼过河 420

spdlog 同步日志库使用详解

spdlog是一个轻量级 C++ 日志库,支持同步日志、异步日志、多线程日志、日志文件轮转和自定义日志格式。对于嵌入式 Linux 应用开发,我建议先使用。它可以帮我们完成:日志级别过滤。时间戳和线程 ID 输出。日志格式化。终端和文件输出。多线程安全写入。日志文件按大小或时间轮转。我们不需要自己实现互斥锁、文件写入、日志格式化和轮转等底层功能。

zengjiajun2006的博客 1120

JavaSE通关手册:数据类型与变量

代码无需额外处理,编译时编译器自动完成转换。

xys_678的博客 221

Java 多态 学习笔记

多态是面向对象编程的重要特性,指同一操作作用于不同对象时产生不同行为。其核心在于“父类引用指向子类对象”,结合方法重写实现运行时动态绑定。多态使代码更灵活、可扩展,如用Person p = new Student();统一处理各类人员注册,无需修改调用逻辑。但多态无法直接调用子类特有方法,需通过instanceof判断并强制转换。编译时看左边(类型),运行时看右边(实际对象),实现解耦与维护便利。

AA_AA114514的博客 367
上一篇: Netty源码分析---executionMask详解
下一篇: Netty源码分析---NioEventLoopGroup深度解析
不早睡不改名@
博客等级 码龄5年 536粉丝 306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值