Lock接口和synchronized最直接的区别在于Lock是显式加锁/解锁,而synchronized是隐式加锁(进入代码块自动加锁,退出时自动释放),因此使用Lock必须手动调用lock()和unlock(),且unlock()须置于finally块中以防异常导致锁不释放。

Lock接口和synchronized最直接的区别在哪
核心区别是 Lock 是显式加锁/解锁,而 synchronized 是隐式(进入块自动加,退出自动释放)。这意味着你必须手动调用 lock() 和 unlock(),且 unlock() 必须放在 finally 块里——漏掉会导致死锁或资源长期被占。
常见错误现象:没写 finally,异常一抛,锁就永远不释放;或者在 lock() 前就 return,根本没拿到锁却写了 unlock()(会抛 IllegalMonitorStateException)。
- 必须用
try-finally包裹,不是try-catch -
ReentrantLock是最常用的实现,支持可重入、公平/非公平模式 - 不支持 synchronized 那种“方法级”语法糖,只能锁代码块
ReentrantLock.tryLock() 什么时候该用
tryLock() 是非阻塞加锁,返回 boolean 表示是否成功抢到锁。适合对响应性敏感的场景,比如实时服务不能卡在锁上等几秒。
典型使用场景:后台任务做状态检查、缓存预热、限流降级逻辑中避免线程长时间挂起。
立即学习“Java免费学习笔记(深入)”;
- 带超时的
tryLock(long, TimeUnit)更实用,比如tryLock(100, TimeUnit.MILLISECONDS) - 如果返回
false,别直接重试,要加退避逻辑(如指数退避),否则可能引发 CPU 空转 - 注意:即使
tryLock()成功,仍需在finally中unlock()
Condition替代wait/notify为什么更安全
Condition 是 Lock 的配套等待通知机制,相比 Object.wait()/notify(),它能绑定多个独立等待队列,避免「通知错对象」问题。
比如生产者-消费者模型里,你可以为「空」和「满」各建一个 Condition:notFull 和 notEmpty,这样 signal() 就只唤醒对应角色的线程。
- 必须由同一个
Lock实例创建:lock.newCondition() -
await()会自动释放锁,被signal()唤醒后重新竞争锁 - 别在
synchronized块里调用Condition方法——会抛IllegalMonitorStateException
公平锁性能差但能防饥饿,真需要吗
构造 ReentrantLock 时传 true 可启用公平模式:new ReentrantLock(true)。它会让等待最久的线程优先获取锁,但每次加锁都要遍历同步队列,开销比默认非公平锁高 2–3 倍。
绝大多数业务场景不需要公平锁。JVM 的 synchronized 和默认 ReentrantLock 都是非公平的,这是性能与调度合理性之间的权衡。
- 只有明确存在「长等待线程反复抢不到锁」的饥饿问题时才考虑
- 高并发写日志、计数器更新等简单临界区,非公平锁吞吐更高
- 公平锁不能完全杜绝饥饿——如果新线程持续高频到来,老线程仍可能饿死
显式锁真正难的不是写法,而是判断「哪里该用 Lock 而不是 synchronized」,以及「要不要拆 Condition、要不要设超时、要不要开公平模式」——这些都得结合具体吞吐、延迟、线程行为来定,没法套模板。










