🚀天下苦阻塞久矣之DeliQueue:Android 17 无锁 MessageQueue 的架构重构

在理解这次重构之前,我们先把时间线拉开。

一、Handler 的四次进化

如果把 MessageQueue 放到时间轴上看,你会发现: 它并不是一套一开始就设计好的系统,而是四代架构不断演进、逐层叠加的结果。


🧱 Android 1.5(纯 Java 期)

能用就行

其实很多人不知道这个版本,在行为上类似一个 BlockingQueue:

生产者投递消息 消费者阻塞等待

  • 单链表
  • wait / notify
  • synchronized

👉 本质:解决线程间通信

笔者在这个版本写过N个APP。

⚙️ Android 2.3(Native 期)

接入操作系统能力

这是一个被很多人低估的版本升级。

  • 引入 epoll
  • Looper 能监听 FD(输入、Binder 等)

意味着什么?

Looper 不再只是"取消息",而是可以同时监听:

输入事件(触摸 / 键盘) Binder 回调 VSYNC 信号 Socket / Pipe

👉 本质:从"消息循环"升级为"事件循环"


🎯 Android 4.1(异步期)

为流畅度而战

这是 Handler 历史上最关键的一次改动之一。

在 Android 4.1 之前,系统普遍存在:

  • UI 卡顿
  • 掉帧
  • 滑动不流畅

原因很简单

普通消息 和 UI 渲染消息 是同一优先级

💥 典型问题

大量业务消息 → 堆满队列

→ VSYNC 渲染消息排在后面

→ UI 延迟执行

→ 掉帧


🚧 解决方案:同步屏障(Sync Barrier)

系统在队列中插入一个"屏障":

屏障之后: ❌ 同步消息被拦住 ✅ 异步消息可以穿透


⚡ 配套机制:异步消息(Async Message)

标记关键消息: 这是高优先级,不要被挡

🔗 组合效果

UI 渲染(VSYNC) → 标记为异步 → 永远不会被普通消息阻塞


🎯 这一步的意义

👉 UI 渲染第一次拥有"绝对优先级"


🚀 直接带来一个时代

🚀 60fps(16ms 帧预算)

💥 Android 17(无锁期)

面向多核时代的彻底重构

  • DeliQueue
  • Lock-Free
  • 三层结构

👉 本质:解决高并发下的锁竞争


一句话串起来:


二、为什么必须重构?

旧版 MessageQueue 的问题不在于"写得不好",而在于:

❗ 它生于单核时代,却跑在多核时代


它的结构是:

  • 单链表
  • synchronized 全局锁

带来的问题:


❌ 1. 入队 O(n)

需要遍历找插入位置


❌ 2. 所有线程争抢一把锁

  • 发消息要锁
  • 删除要锁
  • barrier 也要锁

❌ 3. 优先级反转


Google 用 BigTrace 在大量设备上统计后发现:

👉 这是系统性问题,而不是个别 App Bug


结论只有一个:

❌ 不能优化 ✅ 必须换模型


三、DeliQueue 的核心思想

核心洞察只有一句话:

生产者和消费者的需求根本不同


🧵 生产者(任意线程)

只关心:

  • 快速投递
  • 不阻塞

👉 不关心顺序


🧠 消费者(Looper)

只关心:

  • 按时间有序执行

👉 且只有一个线程,无竞争


那问题来了:

❓ 为什么要用一个结构 + 一把锁,同时满足这两种需求?


四、三层架构:彻底拆分问题

于是,MessageQueue 被拆成三层:


一句话理解:

👉 写的时候不排序,读的时候再排序


五、MessageStack:Lock-Free 并发入口

MessageStack 是所有生产者线程的共同入口,它的核心是一个 Treiber 无锁栈

Treiber 栈的入队逻辑极其简洁:

没有锁,没有等待。失败了就重试,因为失败意味着另一个线程刚刚成功入队了。

👉 至少有一个线程在前进


它的职责只有一件事:

把并发问题压缩成一个 O(1) 的 CAS 操作

至于顺序?不管。


heapSweep:从 Stack 到 Heap 的批量转移

Looper 每次取消息前,会做一件事:

👉 把 Stack 里的消息"扫"进 Heap

关键优化:


✅ 只处理新数据

通过 mLooperProcessed 游标避免重复扫描


✅ 顺手构建双向链表

为后续 O(1) 删除做准备


本质:

👉 延迟排序(Lazy Ordering)


六、MessageHeap:单线程有序调度核心

这里才是真正"排队"的地方。

结构:

👉 最小堆(按 when 排序)


关键点:

❗ 只有 Looper 能访问


结果:

  • 无锁
  • 无竞争
  • 高性能

为什么是两个堆?

不是一个,而是:

  • mSyncHeap
  • mAsyncHeap

解决的问题:

👉 Sync Barrier


旧版:


新版:


一句话:

👉 用结构替代逻辑


七、删除机制:墓碑 + freelist

删除最难,因为:

  • 来自任意线程
  • Heap 只能 Looper 改

解决方案:


① 逻辑删除

② 加入 freelist


③ Looper 统一清理

本质:

跨线程操作 → 延迟为单线程操作


八、WaitState:无锁唤醒协议

核心变量:


两种模式:


💤 Deadline(Looper 在睡)

  • 存唤醒时间

👉 有更早消息 → 唤醒


🏃 Counter(Looper 在跑)

  • 存计数

👉 不唤醒


本质:

👉 一个变量表达整个系统状态


九、insertSeq:保证 FIFO

Treiber 栈无序 → 怎么保证顺序?


解法:


规则:

  • 普通消息:递增
  • 插队消息:负数递减

效果:

bash 复制代码
when 相同 → 按 seq 排

一句话:

👉 用数字替代逻辑


十、VarHandle:性能关键

不用 Atomic 的原因:


实际使用:

  • Acquire(读)
  • Release(写)

效果:

👉 更轻量,更快


十一、整体架构图


十二、本质差异

维度 旧版 DeliQueue
并发 无锁
入队 链表 O(n) 栈 O(1)
排序 入队时 出队前
删除 立即 延迟
barrier 遍历 双堆
唤醒 notify CAS

十三、设计哲学总结

这套系统背后其实只有五个原则:


① 拆

并发 ≠ 排序


② 拖

能晚做就晚做


③ 压

复杂协调用 CAS 表达


④ 单

有序只交给一个线程


⑤ 弱

只用最弱内存语义


十四、最终总结


这是 Android 20 年来:

最重要的一次消息机制重构之一


总结

DeliQueue 做的不是优化,而是把"锁"这个概念,从 MessageQueue 里删除了。

那么MessageQueue 都去除了重锁,那么咱们的应用层就更不该用重锁了。


参考

相关推荐
麦芽糖02192 小时前
python进阶六 正则表达式
android·python·正则表达式
北漂Zachary10 小时前
四大编程语言终极对比
android·java·php·laravel
学习使我健康15 小时前
Android App 启动原理
android·android studio
TechMix15 小时前
【性能工具】atrace、systrace、perfetto抓取的trace文件有何不同?
android·性能优化
张小潇16 小时前
AOSP15 WMS/AMS系统开发 - 窗口层级源码分析
android·前端
努力努力再努力wz18 小时前
【MySQL入门系列】掌握表数据的 CRUD:DML 核心语法与执行逻辑解析
android·开发语言·数据结构·数据库·c++·b树·mysql
zh_xuan20 小时前
Android gradle任务
android·gradle构建
Grackers21 小时前
Android Perfetto 系列 10:Binder 调度与锁竞争
android·binder
李白你好21 小时前
Android 自动化渗透测试指令生成
android·自动化