🚀天下苦阻塞久矣之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 都去除了重锁,那么咱们的应用层就更不该用重锁了。


参考

相关推荐
用户86022504674722 小时前
Jetpack ViewModel 入门与实践
android
随遇丿而安2 小时前
第3周:按钮这件小事,真正麻烦的是“点完以后”
android
峥嵘life3 小时前
五一南昌第三天游玩记录:梅景寻芳,母校忆旧,摩天轮揽夜
android
qq_452396235 小时前
第三篇:《JMeter断言:验证接口响应正确性》
android·jmeter
aqi005 小时前
一文速览 HarmonyOS 6.0.1 引入的十个新特性
android·华为·harmonyos·鸿蒙·harmony
橙子199110166 小时前
Android 第三方框架 相关
android
赏金术士7 小时前
JetPack Compose 弹窗、菜单、交互组件(五)
android·kotlin·交互·android jetpack·compose
海天鹰7 小时前
高版本安卓老应用下面空白
android
猫的玖月7 小时前
(七)函数
android·数据库·sql