在协程成为主流之后,我越来越坚定一个观点:
在现代 Android / Kotlin 项目中,
不要在业务层显式使用 AQS 同步器。
包括:
ReentrantLockCountDownLatchSemaphoreFutureTasksynchronized
不是因为它们不好。
而是因为------
它们属于更低一层的抽象。
一、什么叫"显式使用 AQS"?
不是指你写了多复杂的并发代码。
而是指:
在 Data 层 / UseCase 层直接操作线程同步器。
比如在 UseCase 里:

或者在 Repository 里:

在协程时代,这类代码基本可以判断为:
并发抽象层级发生错位。
问题不在语法。
问题在抽象。
二、问题不在"对错",而在"层级"
AQS(AbstractQueuedSynchronizer)解决的是:
- 线程竞争
- 线程阻塞
- 等待队列管理
- 线程级调度
它属于 JVM 并发模型的底层能力。
而你现在在用什么?
👉 协程。
协程的设计哲学是:
- 挂起,而不是阻塞
- 结构化并发
- 任务级调度
- 线程与任务解耦
当你在协程架构中使用 ReentrantLock,本质是在:
用线程模型解决任务模型的问题。
这就像在 Compose 里手动操作 View。
不是不能。
而是违背抽象边界。
三、线程阻塞 vs 协程挂起
来看最本质的差别。
ReentrantLock 的行为:
- 抢不到锁
- 阻塞线程
- 进入等待队列
- 被唤醒
Mutex 的行为:
- 抢不到锁
- 挂起协程
- 不占用线程
- 恢复 continuation
核心区别只有一个:
是否占用线程。
如果你在 Dispatchers.Default 中大量使用 ReentrantLock,你很可能会:
- 卡住线程池
- 降低并发度
- 产生性能抖动
- 出现难以排查的调度问题
这不是"锁用得不对"。
是抽象层级错位。
四、协程时代真正应该用什么?
在现代 Kotlin 项目里,业务层应该优先使用:
MutexChannelFlowStateFlowasync / await- structured concurrency
比如:

更进一步,可以通过:
- actor 模型
- 单线程 dispatcher
- 状态流建模
来避免显式锁。
这才是协程模型内部的并发方式。
五、什么时候可以用 AQS?
只有两种情况:
1️⃣ 写底层库
- 自定义线程池
- 自定义并发框架
- JVM 基础组件
2️⃣ 强交互 Java 旧系统
- 旧模块是 Java
- 依赖 JUC API
- 桥接遗留系统
否则:
业务层尽量不要出现 AQS 同步器。
六、显式使用 AQS,通常暴露什么问题?
在业务层看到:
ReentrantLockCountDownLatchsynchronized
通常意味着:
- 并发模型没统一
- 协程和线程混用
- 结构化并发没有真正落地
- 分层设计不清晰
这不是"并发很复杂"。
这是:
抽象边界没有建立。
七、协程时代的并发分层
更健康的分层应该是:
业务层 → 协程原语
UseCase → 协程编排
数据层 → 协程 + IO
底层基础库 → JVM 并发原语(可能包含 AQS)
而不是:
UseCase 直接操作线程锁
Repository 手动管理等待队列
AQS 是地基。
业务代码不应该住在地基里。
八、一句话原则
在协程项目中,不要用线程锁解决协程问题。
AQS 没过时。
但它不属于你的业务代码层。
九、真正成熟项目的标志
一个真正成熟的 Kotlin 项目应该是:
- 全面协程化
- 结构化并发
- 无阻塞设计
- 明确抽象边界
- 业务层不感知线程模型
协程不是替代线程。
协程是对线程的抽象升级。
而架构设计真正的能力是:
把低层抽象封装在底层
不让它污染上层业务。
AQS 是基础设施。
业务代码应该站在抽象之上,而不是回到线程时代。