可重入锁本质是线程绑定的state计数器:每次lock()使state+1,unlock()使state-1,仅state==0时才真正释放;必须严格配对调用,且不可跨线程传递。

可重入锁不是“能重复调用lock()”,而是线程级计数器在起作用
很多人以为“同一个线程调两次 lock() 不死锁”就是可重入,其实这只是表象。真正关键的是:ReentrantLock 内部靠 AQS 的 state 字段做**重入计数**,且绑定到当前线程(exclusiveOwnerThread)。每次成功加锁,state +1;每次 unlock(),state -1;只有 state == 0 时才算真正释放锁。
这意味着:
- 嵌套调用必须严格配对——
lock()三次就得unlock()三次,少一次,锁还在你手上;多一次会直接抛IllegalMonitorStateException - 不能跨线程传递锁:A线程
lock()后把锁对象传给B线程,B调unlock()会失败(因为exclusiveOwnerThread != B) -
isHeldByCurrentThread()是唯一安全的“我有没有持锁”判断方式,别用getHoldCount() == 0反推
公平锁 vs 非公平锁:差别就在第一行 CAS 尝试
看源码最直观的分水岭在 NonfairSync.lock() 和 FairSync.lock() 的第一句:
- 非公平锁:
if (compareAndSetState(0, 1))—— 新线程上来就插队抢,抢不到才老老实实进队列 - 公平锁:直接跳过这步,直奔
acquire(1),也就是先查队列里有没有人等着,有就排队,绝不插队
所以“公平”不是指绝对时间公平,而是指「不绕过同步队列」。实际压测中,非公平锁吞吐量通常高 10%~20%,但极端场景下可能让某个线程饿死(比如持续高并发+短临界区)。
立即学习“Java免费学习笔记(深入)”;
构造时选哪个?new ReentrantLock() → 默认非公平,适合大多数业务new ReentrantLock(true) → 公平,仅当你明确需要避免饥饿、且能接受性能折损时启用
为什么 lockInterruptibly() 能中断,而 lock() 不能?
根本区别不在锁本身,而在 AQS 的获取逻辑分支:
-
lock()走的是acquire(1),内部调用tryAcquire()失败后,直接进队列挂起,**忽略中断状态** -
lockInterruptibly()走的是acquireInterruptibly(1),它会在挂起前检查Thread.interrupted(),一旦发现已中断,立刻抛出InterruptedException并退出
典型误用:try { lock.lockInterruptibly(); } catch (InterruptedException e) { /* 忘了恢复中断状态或没清理资源 */ }
正确做法是:捕获后要么向上抛,要么显式 Thread.currentThread().interrupt() 恢复标记,否则下游逻辑可能收不到中断信号。
别漏掉 unlock(),也别在 finally 外写它
这是 ReentrantLock 最容易翻车的地方——synchronized 由 JVM 自动释放,而它全靠你手动。
常见错误:
- 写在 if 分支里,某些路径没执行到
unlock() - 放在 try 块末尾,没包在 finally 中,一旦临界区抛异常,锁永远卡住
- 用在 lambda 或异步回调里,线程切换后
unlock()执行在错误线程上(直接失败)
唯一安全模式:
lock.lock();
try {
// 业务代码
} finally {
lock.unlock(); // 这行必须存在,且不能被条件控制
}注意:如果临界区可能耗时极长(如 IO),考虑用
tryLock(long, TimeUnit) 加超时,避免无限等待拖垮整个线程池。

















