一、核心区别(面试必背,6 大维度清晰对比)
1. 底层实现
-
synchronized :JVM 层面实现,基于 Monitor 监视器锁,是 Java 内置关键字,由 JVM 自动加锁、释放锁。
-
ReentrantLock :JDK 层面实现,基于 AQS 队列同步器 ,是
java.util.concurrent.locks包下的 API 类,需手动lock()/unlock()。
2. 可重入性
两者均支持可重入(同一个线程可重复获取锁,避免死锁)。
3. 锁获取与释放方式
- synchronized :自动加锁,代码块执行完毕 / 抛出异常后自动释放锁,无需手动操作。
- ReentrantLock :必须手动
lock()加锁,unlock()释放锁,且必须在finally中释放,否则极易死锁。
4. 公平锁支持
- synchronized :只支持非公平锁,线程竞争锁时随机抢占,无法保证顺序。
- ReentrantLock :支持公平锁 & 非公平锁 ,构造方法传入
true即为公平锁(按等待顺序获取锁)。
5. 高级特性
- synchronized:无额外特性,仅支持基础互斥锁。
- ReentrantLock :支持 限时等待锁 、可中断锁 、多个 Condition 条件队列,适合复杂同步场景。
6. 性能表现
- JDK 1.6 以前:synchronized 性能远低于 ReentrantLock。
- JDK 1.6 及以后:synchronized 引入偏向锁、轻量级锁、锁升级机制,高并发场景下性能与 ReentrantLock 接近。
二、核心 API 与使用示例
1. synchronized 示例
java
// 同步方法
public synchronized void method() {
// 临界区代码
}
// 同步代码块
public void method() {
synchronized (this) {
// 临界区代码
}
}
2. ReentrantLock 示例
java
private Lock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 临界区代码
} finally {
// 必须在finally释放,避免异常导致锁泄露
lock.unlock();
}
}
三、适用场景
1.优先使用 synchronized
- 简单同步场景,代码简洁、无需手动释放锁,避免人为失误。
- 无需公平锁、条件等待等高级特性。
- 追求代码简洁、减少样板代码。
2.优先使用 ReentrantLock
- 需要公平锁,保证线程按顺序获取锁。
- 需要限时获取锁 (
tryLock(long timeout, TimeUnit unit)),避免无限阻塞。 - 需要可中断锁 (
lockInterruptibly()),防止线程长时间卡死。 - 需要多个 Condition 条件队列,实现精准线程唤醒(如生产者消费者模式)。
四、面试高频追问
追问 1:什么是可重入锁?为什么要设计成可重入?
- 可重入锁:同一个线程可多次获取同一把锁,不会自己锁死自己。
- 目的:避免递归调用、多层嵌套同步代码时出现死锁。
追问 2:ReentrantLock 为什么必须在 finally 里 unlock?
- 若临界区代码抛出异常,锁无法释放,会导致锁泄露,其他线程永久阻塞。
追问 3:synchronized 锁升级过程是什么?
无锁 → 偏向锁 → 轻量级锁 → 重量级锁竞争少时用轻量级锁(自旋),竞争激烈升级为重量级锁(阻塞)。
追问 4:Condition 相比于 synchronized 的 wait/notify 有什么优势?
synchronized只有一个等待队列,唤醒时随机或全部唤醒。ReentrantLock可创建多个Condition,实现分组等待、精准唤醒,避免无效唤醒,性能更高。
五、总结
synchronized 是 JVM 内置关键字,自动加解锁、使用简单;ReentrantLock 是 JDK API,支持公平锁、限时等待、多条件队列等高级特性。简单场景用 synchronized,复杂同步场景选 ReentrantLock。