“一把锁”引发的六年工程:揭秘 IntelliJ 系 IDE 响应性优化的底层逻辑
摘要
这篇技术博文记录了我们为提升基于 IntelliJ 的 IDE 界面响应速度所做的努力。这是一项持续多年的工程,目标是打破几个根深蒂固的架构瓶颈。项目目前仍在推进中——我们已经构建了全新的工具和 API,将性能敏感型操作从 UI 线程中剥离出来。这一改变让 UI 线程持有写入锁的时间缩短到了原来的三分之一。如果你对技术细节不感兴趣,可以直接跳到文末看结果图表。
基于 IntelliJ 的 IDE 被吐槽最多的,是性能问题。
我们清楚。也一直在想办法。
但这件事并不简单:IntelliJ Platform 已经有 25 年的历史了,不少架构层面的决策早已深度嵌入。正是这些历史决策,让某些优化工作举步维艰。
“读写锁 + UI 线程”
的致命缠斗
IntelliJ Platform 是一个以单一读写(RW)锁为核心构建的多线程框架。IDE 运行依赖三个核心数据结构:语法树(PSI)、文件的文本视图(文档子系统),以及操作系统文件系统的视图(虚拟文件系统,即 VFS)。对这些结构的访问,全部由 RW 锁守护。
操作分为读操作和写操作:任意时刻只允许存在一个写操作;多个读操作可以并行执行,但读操作和写操作不能同时进行。
IDE 同时也是一个 UI 应用,因此依赖 UI 框架。IntelliJ Platform 使用的是 Java AWT,它只有一个 UI 线程——事件分发线程(EDT)。这个线程负责处理用户输入和界面绘制,Java 也允许在这里运行业务逻辑。EDT 的处理速度直接决定了应用的响应感受:它越快,IDE 用起来就越流畅。
卡顿的根源正在这里。
写操作本身就可能引发卡顿——重新解析语法树、更新文件系统视图,这些操作的开销本来就不低。更隐蔽的问题是等待写入锁:由于读写操作不能并行,启动写操作就意味着要等所有进行中的读操作全部完成。我们为此做了大量工作,让读操作支持取消,但问题并未彻底消失——只要有一个读操作不配合,整个 IDE 都会被拖慢。
由此,我们确定了一个核心目标:把写操作从 UI 线程中移走。
“支持后台写”
(2019 年开始)
支持后台写操作的工作于 2019 年启动,由 Valentin Fondaratov、Andrew Kozlov 和 Peter Gromov 主导推进。
长期以来,运行在 EDT 上的代码可以随意访问 IntelliJ Platform 的模型,十分方便。但一旦引入后台写操作,这种便利就成了隐患:UI 代码不能再默认"不用协调也能安全访问模型"了。为了保持兼容性,我们必须在让这些假设变得明确的同时,确保大量现有 UI 代码依然能正常运行。
还有另一个棘手之处:EDT 上的代码可以直接启动写操作,而普通的显式读操作却做不到这一点——写操作无法在读操作执行过程中直接插入。
于是,写意图(write-intent) 的概念应运而生。写意图是一种特殊的锁状态:它允许并行读操作继续进行,但同一时刻只能由一个线程持有,并且可以原子性地升级为完整写操作。这对于"可能需要转变为写操作"的 EDT 代码来说,是理想的过渡机制。将这一机制引入平台,成为实现后台写操作的关键一步。
然而,项目在 2020 年被迫暂停。原因很现实:需要修改的代码量太过庞大。编辑器等诸多 UI 组件长期依赖"通过 EDT 访问模型"这一默认假设,积重难返。
大规模重构(2022)
项目暂停了,但没有被放弃。
2022 年,Lev Serebryakov 和 Daniil Ovchinnikov 重启了这项工作。
这一阶段的核心任务是重构 IntelliJ Platform,把大量平台曾经隐式依赖的假设明确地表达出来,从而减少 UI 驱动代码路径中对隐式加锁的依赖。
另一项重要进展来自与 JetBrains Research 团队的合作。旧有的锁实现假设写操作只在 EDT 上执行;而把写操作移入后台,则需要一套全新的锁机制——原有的 ReentrantReadWriteLock 已经无法胜任。合作的成果是一种全新的可取消锁,如今已成为平台的核心基础设施(详见这篇研究论文)。
这一阶段的工作一直持续到 2024 年底。
一把锁不够用了(2025)
2025 年初,Konstantin Nisht 接手了项目这部分的工作。彼时,我们已基本准备好运行第一批后台写操作——但还剩一个大麻烦没有解决:模态(modality)。
IDE 中有些 UI 元素需要独占用户的注意力,阻止他们操作其他任何内容,比如 Settings(设置)对话框。在 IntelliJ Platform 中,模态不仅影响 UI,也影响数据模型:当模态对话框打开时,与之无关的写操作不应该被允许启动。过去,EDT 调度器承担了大部分这类控制逻辑——在模态对话框激活期间,非模态上下文下发起的 UI 任务根本不会运行。
但后台写操作天然不适配这套模型。
如果 EDT 在持有写意图的同时弹出模态对话框,此时若某处天真地尝试启动后台写操作,就会死锁。而与此同时,我们还必须保证对话框内部的计算任务能够正常进行,不被外部无关的操作打断。
为此,我们引入了模态感知锁定策略,将模态对话框内部与外部的操作彻底分离。这样既保留了模态对话框原有的保障机制,又让后台写操作得以正常运行。
这一机制同样支持嵌套模态场景——而真实的模态工作流往往不是单层的,这一点不能忽视。
完成这项适配后,我们终于在后台运行了第一批写操作。
在不破坏插件的前提下
迁移任务
最初的写操作迁移相对顺利。这些操作位于 Workspace Model 中,主要用于使部分缓存失效,"搬"起来并不费力。
接下来,我们挑战了更硬的骨头:VFS 刷新。
VFS 刷新负责将操作系统的文件修改事件同步到 IDE 内部的数据结构。它不只是应用事件那么简单——刷新过程还会触发监听器(listener),也就是那些响应文件系统变化的插件代码。按照惯例,VFS 刷新在写操作中运行,监听器也在其中被调用。
这就带来了兼容性问题。多年来,大量监听器代码默认自己会在 EDT 上运行,有些监听器甚至直接操作 UI。更麻烦的是,其中许多监听器存在于我们无法控制的第三方插件中——我们没法直接改变执行模型,然后祈祷一切都还能跑起来。
所以,挑战不只是"把写操作移到后台",而是在不让海量现有插件代码崩掉的前提下做到这一点。
思路本身并不复杂:保持写操作在后台运行,但遇到有兼容性需求的监听器时,通过 invokeAndWait(...) 把任务同步交还给 EDT 处理。Swing 提供了这样的机制。
但这个看似简单的方案里藏着死锁的陷阱:如果后台写操作尝试同步向 EDT 交接任务,而此时 EDT 正因等待某把锁而阻塞,整个 IDE 就会冻结。
为了绕开这个陷阱,我们引入了一套内部兼容机制,允许特定 UI 事件在等待期间继续得到处理。这为我们打开了渐进式迁移的通道:先把最耗时的写操作移出 EDT,同时为仍依赖 EDT 的监听器保留兼容性,逐步推进,不用一次性全部切换。
这最终成为整个项目最关键的一环——它让我们能够增量地迁移监听器、维持外部插件的兼容,同时通过优先迁移最慢的部分,拿到了绝大部分的性能收益。
VFS 刷新完成后,我们接着迁移了文档提交(document commit) 流程——即通过文档重建 PSI 的过程。有了前面搭好的核心写操作机制,这次迁移顺畅得多。
后续:
我们还不能“彻底摆脱锁”
后台写操作不是万能药。
它能减少 EDT 花在执行写操作上的时间,但并不能自动消除 EDT 花在等待锁上的时间。
即便写操作已经移到后台,EDT 本身仍然可能被要求获取读锁或写意图锁。只要写操作正在运行,或正在等待写锁,这些请求就依然可能冻结 UI。
这引出了项目的第二条主线:尽量减少 EDT 上的锁获取操作。
问题最突出的地方是编辑器。编辑器需要根据光标位置、折叠状态、文档内容等模型数据在屏幕上绘制内容,而文档修改受 RW 锁保护,编辑器在 EDT 上仍然需要访问这些数据。很长一段时间里,这意味着编辑器代码里到处都有读操作——连绘制路径里都有。这是个严重的问题:绘制可能随时触发,这意味着编辑器可能恰好在我们最希望 UI 线程保持空闲的那一刻,开口要读锁。
对此,我们做了一个务实的权衡:放宽编辑器相关 EDT 路径的部分锁要求,同时把一部分文档写操作保留在 EDT 上,以维持数据一致性。编辑器绘制因此减少了对锁的依赖——尽管将所有文档修改移出 EDT 的目标还没达到,那部分工作仍在进行中。
EDT 上锁压力的另一个来源是异步计算 API。为了保持兼容性,很多异步计算依然与写意图锁的获取绑定在一起,导致 EDT 可能在无法预测的时刻被卡住。
这里的关键洞察其实很朴素:如果有人把一个任务调度到 UI 线程异步执行,他通常并不在乎任务精确到微秒级的启动时间。这意味着,我们没必要让 EDT 一直阻塞等待写意图锁——很多情况下,直接把计算推迟到锁可用的时候再执行就够了。对平台的 UI 调度逻辑做了一些调整之后,这个问题已经大幅缓解。
成果、展望与致谢
后台写操作触及 IntelliJ Platform 的底层契约,本身就错综复杂。我们还在持续构建新的 API 和工具,帮助插件把自身逻辑与 EDT 解耦。工作尚未完成,但我们已经取得了实质性进展。
衡量成效的指标,是我们追踪的 EDT 花在写操作上的时间占比。下图数据来自各版本发布一周后的采集结果:
以 2025.2 为基准:有 1% 的用户,其 5% 的 UI 时间都消耗在了写操作上。到了 2025.3,同等比例的用户只有 3% 的 UI 时间花在写操作上。从整体均值看,EDT 上写锁占用 UI 时间的比例从 2025.2 的 1.8276% 下降到了 2025.3 的 0.5298%,降幅约三分之二。
接下来,我们的重点是进一步减少 EDT 上的写意图使用量,最终目标是在输入等日常交互操作中彻底消除锁等待。这条路并不好走——Actions、PSI、Documents 等基础结构都需要重新审视。但我们认为,这是可以做到的。
最后,感谢所有直接或间接参与这项工作、在前文中尚未被提及的贡献者:Anna Saklakova、Dmitrii Batkovich、Vladimir Krivosheev、Moncef Slimani、Lev Serebryakov 和 Nikita Koval,以及更多幕后的贡献者。本文最初由 Konstantin Nisht 撰写为内部文档,后经 Patrick Scheibe 改编为公开博客文章——这次的破坏性改动,比平时少了不少。
声明:本文基于 JetBrains 官方英文博客原文,经 AI 辅助翻译与内容编排后发布。文中配图已使用 AI 工具进行本地化处理。如有翻译偏差或技术表述不准确之处,欢迎在评论区留言指正,我们将及时更新。
更多阅读推荐
产品动态
“非商用免费” IDE 家族
申请免费学生许可证
👉 戳这里看保姆级申请攻略 👈