Lock接口比synchronized多出尝试非阻塞获取锁、响应中断、超时放弃、单锁绑定多个Condition等控制能力,适用于分布式协调、资源池管理等场景。

Lock 接口比 synchronized 多出哪些控制能力
单纯加锁不是目的,关键是能应对 synchronized 做不了的事:比如尝试获取锁不阻塞、响应中断、超时放弃、多个条件变量绑定同一个锁。这些在分布式协调、资源池管理、实时性要求高的场景里很关键。
常见错误是直接用 ReentrantLock 替换 synchronized 却忽略手动释放——synchronized 是 JVM 自动释放的,而 Lock 必须显式调用 unlock(),且必须放在 finally 块里,否则极易死锁。
- 锁可重入,但不会自动释放:必须配对
lock()和unlock() -
tryLock()返回boolean,适合非阻塞抢占;tryLock(long, TimeUnit)支持超时,避免无限等待 -
lockInterruptibly()让线程能被interrupt()中断,synchronized不支持这点 - 一个
ReentrantLock可关联多个Condition,实现类似「生产者等队列不满、消费者等队列不空」的精细唤醒
什么时候该用 ReentrantLock 而不是 synchronized
不是“更高级就该用”,而是看需求是否踩中 synchronized 的硬伤。比如你写一个带超时的数据库连接获取逻辑,或需要在线程被中断时立刻退出临界区,或要避免 notifyAll() 唤醒所有等待线程带来的虚假唤醒问题。
性能上,JDK 1.6+ 后 synchronized 做了大量优化(偏向锁、轻量级锁、自旋),在无竞争或低竞争场景下,它通常比 ReentrantLock 更快、更省内存。别为了“看起来更可控”就默认选 Lock。
立即学习“Java免费学习笔记(深入)”;
- 需要公平锁策略(按等待顺序分配):
new ReentrantLock(true),synchronized没有公平模式 - 需要在持有锁期间做复杂判断并决定是否继续等待:用
Condition.await()+signal()精准控制 - 锁持有时间长,且可能被外部中断打断:必须用
lockInterruptibly() - 仅简单同步方法体或代码块,无特殊需求:优先用
synchronized,更简洁、不易出错
ReentrantLock + Condition 实现多条件等待的典型陷阱
一个 ReentrantLock 可以 new 出多个 Condition,但每个 Condition 必须由同一个 Lock 实例创建,不能混用。最常踩的坑是:用错了 Condition 实例去 signal(),或者在未持有锁时调用 await(),会直接抛 IllegalMonitorStateException。
另一个隐性问题是 signal() 不保证唤醒哪个线程,如果业务逻辑依赖唤醒顺序(比如严格 FIFO),得自己维护等待队列,Condition 本身不提供这种保障。
-
await()会自动释放锁,并把当前线程挂起;被signal()唤醒后需重新竞争锁,成功后才继续执行 - 永远用
while判断条件,不用if——因为唤醒后条件可能已变,或发生虚假唤醒 -
signalAll()唤醒所有等待该Condition的线程,开销大,只在确实需要广播时用 - 不要在
finally块里调用await(),那是释放锁的地方,不是等待的地方
LockSupport.park/unpark 为什么不是替代方案
LockSupport 是 Lock 和 synchronized 的底层支撑,但它本身不提供原子性、可重入、公平性等语义。单独用 park() 和 unpark() 写同步逻辑,相当于徒手造轮子:你要自己管理线程状态、处理中断、保证唤醒时机、避免丢失信号——这些恰恰是 ReentrantLock 已经封装好的。
它的正确用途是构建更底层的同步器(比如 AQS),而不是日常业务代码里的“轻量锁”。看到有人用 LockSupport 替代 Lock,基本等于把刹车片拆下来当方向盘用。
-
unpark()可以先于park()调用,信号不会丢失,这是它和Object.wait()的关键区别 - 没有锁的语义,无法保证临界区内的操作不被并发修改
- 没有条件等待机制,无法实现「等某个布尔条件为真」这样的业务逻辑
- 调试困难:线程阻塞在
park()时堆栈不体现业务上下文,排查比wait()或await()更费劲
真正难的不是选 Lock 还是 synchronized,而是想清楚:你到底在保护什么?是单个变量的读写,还是跨多个对象的状态一致性?后者往往意味着你需要把锁的粒度、持有范围、异常路径全部画出来,而不是靠接口灵活性掩盖设计缺陷。










