无处生还
76.43M · 2026-04-23
文章内容收录到个人网站,方便阅读:hardyfish.top/
ReentrantLock 的线程安全,本质由两件事托底:互斥与内存可见性。
互斥保证同一时刻只有一个线程进入临界区(critical section,受保护的共享资源访问区);
内存可见性保证临界区内对共享变量的写入,在解锁后对随后获得锁的线程可见。
互斥:基于 AQS 的“独占状态”与 CAS 争用
ReentrantLock 的底层是 AQS(AbstractQueuedSynchronizer,同步器框架) 。AQS 用一个 state 整数表示同步状态,对独占锁而言通常含义是“是否被占用/占用次数”。
抢锁:线程通过 CAS(Compare-And-Swap,比较并交换)尝试把state从0改为1(或从当前值递增)。
锁的排他性:只有 owner 才能继续执行临界区代码;非 owner 必须等待 state 归零并被唤醒后再竞争。
这种设计的关键点在于:对 state 的修改使用原子操作(CAS),从而在高并发下也能可靠地裁决“谁赢得锁”。
可重入:用“持有者 + 计数”避免自我死锁
“可重入(reentrant)”指同一线程在持有锁时再次 lock() 不会被自己阻塞。
ReentrantLock 通过两类信息实现:
持有者线程记录:AQS 维护当前独占持有者(owner thread)。
重入次数计数:state充当计数器。
state 从 0 变为 1,owner 设为当前线程。state++。state--;直到减到 0 才真正释放所有权并唤醒队列中的后继线程。因此,临界区内调用链较深、方法间层层加锁时,不会发生“自己把自己锁死”的问题。
阻塞与唤醒:队列化等待减少竞争风暴
当抢锁失败,线程不会无休止忙等,而是进入 AQS 队列并在合适时机被唤醒:
park(挂起),释放 CPU。state 释放到 0,再按策略唤醒队列中的后继节点竞争。队列化的价值在于把竞争从“所有线程同时冲刺”变成“排队接力”,显著降低上下文切换与缓存抖动带来的损耗。
内存可见性:lock/unlock 的 happens-before 语义
线程安全不只需要“同一时刻只有一个线程进来”,还需要“前一个线程写过的数据,后一个线程读得到”。
ReentrantLock 提供与 synchronized 类似的内存语义:
state 等字段的 volatile 语义 与 CAS/park-unpark 等操作在 Java 内存模型下形成的屏障效果。直观理解:解锁像“刷盘提交”,加锁像“重新加载最新版本”,保证共享变量不会读到旧值。
公平与非公平:安全性相同,调度策略不同
ReentrantLock 有两种策略:
两者都满足互斥与可见性要求,线程安全语义一致,差别在于获得锁的次序与性能特征。
关键用法约束:释放必须与获取配对
ReentrantLock 的线程安全依赖“锁的协议”被正确遵守:
finally 中释放,避免异常路径遗留锁导致其他线程永久阻塞。这套机制合起来,使 ReentrantLock 在并发访问共享资源时,既能保证互斥,又能保证内存可见性,从而实现可验证的线程安全。