极高 进阶
ReentrantLock与synchronized对比#
一句话答案#
ReentrantLock 支持公平锁/可中断等待/超时获取/多条件变量,synchronized 是关键字自动释放更简洁。
核心要点
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 实现层面 | JVM 内置关键字,C++ 实现 | Java 代码实现(AQS) |
| 锁获取 | 自动(进入同步块时) | 手动(lock()) |
| 锁释放 | 自动(退出同步块/异常时,由 JVM 保证) | 手动(unlock(),必须写在 finally 中) |
| 是否可中断 | 不可中断(lock() 会无限等待) | 可中断(lockInterruptibly()) |
| 是否可超时 | 不支持 | 支持(tryLock(timeout, unit)) |
| 是否公平 | 非公平(不保证等待顺序) | 可选(new ReentrantLock(true) 为公平锁) |
| 条件变量 | 只有一个隐式的(wait/notify) | 多个(newCondition(),精确唤醒) |
| 性能 | JDK 6 后优化,差距已不大 | 差距不大,高竞争场景略优 |
| 锁升级 | 支持(偏向→轻量→重量) | 不支持(直接 CAS + AQS) |
正确使用 ReentrantLock 模板:
ReentrantLock lock = new ReentrantLock();
lock.lock(); // 获锁
try {
// 业务逻辑
} finally {
lock.unlock(); // 必须在 finally 中释放,防止异常导致永久持锁
}java选择建议:
- 大多数场景用
synchronized(更简洁,JVM 自动管理,不会忘记释放) - 需要以下特性时用
ReentrantLock:超时获取锁、可中断等待、公平锁、多个条件变量
面试回答(2分钟版)
ReentrantLock 和 synchronized 都是可重入的互斥锁,但实现层面和功能丰富度不同。synchronized 是 JVM 内置关键字,通过 monitorenter/monitorexit 字节码实现,锁的获取和释放完全自动,即使发生异常 JVM 也能保证释放,JDK6 之后引入了偏向锁、轻量级锁等优化,性能已经很好。ReentrantLock 是 Java 层面基于 AQS 实现的,需要手动 lock/unlock 且 unlock 必须写在 finally 块中,但它提供了四个 synchronized 不具备的能力:一是可中断等待 lockInterruptibly(),二是超时获取 tryLock(timeout, unit),三是可以选择公平锁 new ReentrantLock(true),四是支持多个 Condition 条件变量实现精确唤醒。实际开发中大多数场景我会优先用 synchronized,代码更简洁也更安全;只有在需要超时获取、可中断等待、公平调度或多条件变量时才会切换到 ReentrantLock。
追问与易错
追问方向:
- “什么时候必须用 Lock?”→ 需要超时获取锁(tryLock)、可中断等待(lockInterruptibly)、公平锁、多个 Condition 精确唤醒时,synchronized 无法满足
- “公平锁为什么性能差?”→ 每次获锁都要检查 AQS 等待队列是否有排队线程,不允许插队,导致大量线程上下文切换;非公平锁允许新线程直接 CAS 抢锁,减少切换开销
- “怎么实现可重入的?”→ AQS 的 state 字段做重入计数,每次 lock 时检查持有者是否是当前线程,是则 state+1;unlock 时 state-1,减到 0 才真正释放锁
易错点:
- ❌ ReentrantLock 一定比 synchronized 好——简单场景 synchronized 更安全
- ❌ 忘记在 finally 中 unlock