直接用unsafe.compareandswapint易卡死,因其不阻塞且失败后盲目自旋会吃满cpu;必须配合locksupport.park/unpark实现阻塞等待,并用cas设置等待标记以防唤醒丢失。

为什么直接用 Unsafe.compareAndSwapInt 容易卡死
因为 CAS 本身不阻塞,失败后若盲目重试(比如写个 while(true)),会疯狂自旋,吃满 CPU,而且根本没让出线程调度权。更麻烦的是,如果临界区操作稍长,其他线程在等待时完全无法被唤醒——你得自己搭“等-唤”机制,不能只靠 CAS。
实操建议:
- 必须配合
LockSupport.park()实现真正阻塞,而不是空转 - CAS 只用来争抢锁状态(如用一个 int 字段表示 0=无锁,1=已锁),不是用来做业务逻辑同步的
- 每次 park 前要确保当前线程是合法等待者,否则可能 park 了却没人 unpark,永久挂起
- 注意
Unsafe的字段偏移量必须用objectFieldOffset动态获取,硬编码值在不同 JVM 上会错
怎么用 LockSupport 避免丢失唤醒
常见错误是先 park 再检查状态,结果刚 park 就有线程 unlock 并 unpark,但此时还没注册等待,unpark 生效但无效果(unpark 是“发一次票”,park 是“兑一次票”,票不能攒)。
实操建议:
- 必须在 park 前用 CAS 设置等待标记(比如把当前线程存入 waiters 链表头),再检查是否该 park
- unlock 时要遍历等待队列,对每个线程调用
LockSupport.unpark(t),不能只 unpark 第一个 - 避免在 park 后直接 while(true) 重试 CAS,应结合
Thread.interrupted()检查中断,及时退出 -
LockSupport.park()返回后不保证锁已被获取,仍需重新 CAS 尝试,这是标准的“check-then-act”模式
为什么不能用 volatile int state 替代 CAS 字段
volatile 只保证可见性和禁止重排序,不保证原子性。比如 state++ 是读-改-写三步,在多线程下必然丢更新;即使写成 state = 1,也无法区分“我设的”和“别人设的”,导致 unlock 时误释放。
实操建议:
- 锁的获取/释放必须是原子的“比较并设置”,只能靠
Unsafe.compareAndSwapInt或AtomicInteger.compareAndSet - 如果不用
Unsafe,可用AtomicInteger模拟,但要注意其getAndIncrement等方法不适用于锁语义,必须用compareAndSet(0, 1)这类判断式写法 - 别试图用
synchronized包裹 CAS 操作来“保险”——那等于套娃,既失去轻量优势,又可能引发死锁
实际写一个最小可运行的自旋+阻塞混合锁
核心逻辑就三块:尝试 CAS 获取、失败后 park、unlock 时 unpark 头节点。下面这个版本去掉异常处理和公平性,仅保留最简骨架,能跑通但不推荐直接上线。
public class SimpleLock {
private volatile Thread owner;
private final AtomicInteger state = new AtomicInteger(0);
private final AtomicReference<Thread> head = new AtomicReference<>();
public void lock() {
Thread me = Thread.currentThread();
if (state.compareAndSet(0, 1)) {
owner = me;
return;
}
// 入队
Thread old;
do {
old = head.get();
Thread currentHead = head.compareAndSet(old, me) ? old : null;
} while (currentHead == null);
// 自旋 + park
while (!state.compareAndSet(0, 1)) {
if (Thread.interrupted()) throw new RuntimeException("Interrupted");
LockSupport.park();
}
owner = me;
head.set(null); // 清头
}
public void unlock() {
if (Thread.currentThread() != owner) return;
state.set(0);
Thread h = head.get();
if (h != null) LockSupport.unpark(h);
}
}
关键点在于:head 是等待队列的简易实现,但真实场景中要用 Unsafe 操作链表节点,且必须用 UNSAFE.putOrderedObject 控制写顺序;另外,上面代码没处理多个等待者竞争 unpark,实际中需要循环 unpark 所有排队线程或改用 CLH 风格。
最易被忽略的是:park/unpark 的配对不是严格一对一,一个 unpark 可以抵消之前或之后的一次 park,所以状态检查和 park 必须在同一个内存屏障上下文中完成,任何漏掉的 volatile 读写都可能导致永远阻塞。










