MinimalLock通过volatile状态位、有限自旋与LockSupport.park/unpark实现极简同步,仅支持单等待者且无重入/公平性/中断,比ReentrantLock节省内存与CPU开销。

直接用 LockSupport.park 配合自旋构建“比 ReentrantLock 更轻量级”的同步器,这个目标本身需要谨慎理解——ReentrantLock 本身已是高度优化的 AQS 实现,其底层就依赖 LockSupport.park/unpark 和 CAS 自旋。真正可做的不是“替代 AQS”,而是**剥离重功能、聚焦极简场景**,比如:单线程写+多线程读、无重入需求、无公平性要求、不需条件队列、不需中断响应。这种情况下,一个仅含“状态位 + 自旋 + park”三要素的手写同步器,确实能比完整 ReentrantLock 节省内存开销(无 AQS 队列节点)、减少分支逻辑、避免 volatile 写放大。
核心思路:用 volatile 状态控制自旋边界,park 仅作兜底
ReentrantLock 的重量主要来自 AQS 的双向队列管理、Node 节点分配、多种 acquire/release 路径、中断/超时处理等。而轻量同步器只做一件事:确保临界区最多被一个线程执行。关键设计是:
- 用一个
volatile int state表示锁状态:0=空闲,1=已占用 - 获取锁时先自旋若干次(例如 100–500 次),用 CAS 尝试将 state 从 0 设为 1
- 自旋失败后,再调用
LockSupport.park()进入阻塞,避免 CPU 空转 - 释放锁时设
state = 0,并用LockSupport.unpark(waiter)唤醒一个等待者(需记录等待线程)
必须解决的两个关键细节
1. 如何知道该 unpark 哪个线程?
ReentrantLock 的 AQS 队列天然维护了等待顺序,而手写同步器没有队列。最简方案是只支持“最多一个等待者”:用 volatile Thread waiter 字段记录最近调用 park 的线程。释放锁时,若 waiter != null,则 unpark 它,并清空 waiter。
2. 自旋次数怎么定?
太少 → 过早 park,增加上下文切换;太多 → 浪费 CPU。实测经验:在主流 JVM(JDK 17+)和 x86 多核机器上,100–200 次 CAS 自旋是较优平衡点。可用常量配置,或根据 CPU 核心数动态调整(如 Math.min(256, Runtime.getRuntime().availableProcessors() * 64))。
一个可运行的 MinimalLock 示例
以下是一个无重入、无公平性、无中断支持的极简锁实现:
UnsafeUtils.getUnsafe()
- 无队列对象分配:AQS 每次入队都 new Node(),MinimalLock 完全无堆对象创建
-
无 volatile 写扩散:ReentrantLock 释放锁时需更新 Node.waitStatus、修改 tail、唤醒前驱等多个 volatile 写;MinimalLock 只写
state=0和waiter=null两次 - 无复杂状态机:不区分独占/共享模式、不处理中断传播、不维护 acquire queue 的 prev/next 指针
- 内存 footprint 极小:仅 2 个字段(int + Thread 引用),而 ReentrantLock 实际持有 AbstractQueuedSynchronizer 实例(至少 10+ 字段 + 隐式队列头尾)










