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 示例
以下是一个无重入、无公平性、无中断支持的极简锁实现:
public class MinimalLock {
private volatile int state = 0; // 0: free, 1: locked
private volatile Thread waiter = null; // 最近 park 的线程(仅支持单等待者)
<pre class="brush:php;toolbar:false;">public void lock() {
// Step 1: 快速尝试(非自旋)
if (state == 0 && compareAndSetState(0, 1)) {
return;
}
// Step 2: 自旋尝试
for (int i = 0; i < 150; i++) {
if (state == 0 && compareAndSetState(0, 1)) {
return;
}
Thread.onSpinWait(); // JDK9+ 提示 CPU 自旋优化
}
// Step 3: park 兜底
waiter = Thread.currentThread();
while (!compareAndSetState(0, 1)) {
LockSupport.park(this); // 阻塞,直到被 unpark 或中断
if (Thread.interrupted()) {
throw new RuntimeException("Interrupt not supported");
}
}
waiter = null;
}
public void unlock() {
state = 0;
if (waiter != null) {
LockSupport.unpark(waiter);
}
}
private boolean compareAndSetState(int expect, int update) {
return UNSAFE.compareAndSwapInt(this, STATE_OFFSET, expect, update);
}
private static final sun.misc.Unsafe UNSAFE;
private static final long STATE_OFFSET;
static {
try {
UNSAFE = UnsafeUtils.getUnsafe();
STATE_OFFSET = UNSAFE.objectFieldOffset(MinimalLock.class.getDeclaredField("state"));
} catch (Exception e) {
throw new Error(e);
}
}}
注意:UnsafeUtils.getUnsafe() 是封装了获取 Unsafe 实例的工具方法(通过反射绕过构造限制),实际使用需确保 JVM 允许。
它比 ReentrantLock “轻”在哪里?
- 无队列对象分配: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+ 字段 + 隐式队列头尾)

















