在 Java 并发编程中,synchronized 和 ReentrantLock 都是用于保证线程同步的常用机制,但它们在底层实现、功能特性和适用场景上有明显区别。
一、核心区别
| 特性 | synchronized |
ReentrantLock |
|---|---|---|
| 实现层级 | JVM 内置关键字,通过监视器(Monitor)实现 | JDK 提供的 API 级别的锁(java.util.concurrent.locks 包) |
| 锁的获取与释放 | 自动获取 / 释放(由 JVM 管理,发生异常时 JVM 会自动释放锁) | 需手动调用 lock() 和 unlock(),通常在 finally 块中释放,否则可能死锁 |
| 可重入性 | 支持(同一个线程可多次进入同一把锁的同步块) | 支持(同一线程可多次调用 lock(),需对应次数 unlock()) |
| 可中断获取锁 | 不支持(线程在等待锁时无法被中断,除非中断后抛出 InterruptedException 的某些情况,但通常不够灵活) |
支持 lockInterruptibly(),等待锁的线程可响应中断 |
| 超时获取锁 | 不支持 | 支持 tryLock(long timeout, TimeUnit unit),超时未获取到锁返回 false |
| 公平锁 | 非公平锁(JVM 不保证等待时间最长的线程优先获得锁) | 默认非公平,但构造时可指定 true 创建公平锁 |
| 条件变量 | 每个对象只有一个等待队列(通过 wait() / notify() / notifyAll()) |
支持多个 Condition 对象(newCondition()),可精细控制不同条件下的线程等待与唤醒 |
| 性能 | 早期性能较差,经过优化后(偏向锁、轻量级锁等)在大部分场景下与 ReentrantLock 相差不大 |
性能稳定,但在高度竞争下由于需要 CAS 和队列维护,可能略高于 synchronized(现代 JVM 中差距很小) |
| 调试友好 | 在线程 dump 中能清晰显示哪些帧持有锁以及等待锁的线程 | 同样能看到,但需要区分 AbstractQueuedSynchronizer 状态 |
二、使用场景
1. synchronized 适用场景
- 简单同步逻辑:只需保护临界区,不需要高级功能(如中断、超时)。
- 代码简洁优先:利用 JVM 自动管理锁,降低出错概率。
- 使用 wait/notify 机制:操作简单,一个条件队列足以满足需求。
- 无锁竞争或低竞争环境:JVM 的偏向锁、轻量级锁优化效果明显。
- 不易出错的场景:避免手动释放锁可能导致的死锁问题。
java
// 示例:synchronized 保护共享变量
public synchronized void increment() {
counter++;
}
2. ReentrantLock 适用场景
- 需要可中断的锁获取 :避免死锁时,可用
lockInterruptibly()让等待线程响应中断。 - 需要超时获取锁 :使用
tryLock(long timeout, TimeUnit unit)避免线程永久阻塞。 - 公平锁需求:某些业务需要按照线程等待顺序获得锁(但公平锁通常降低吞吐量,需权衡)。
- 多个条件队列:例如生产者-消费者模式,需要分别控制"不满"和"不空"两个条件。
- 尝试非阻塞获取锁 :
tryLock()可在获取不到锁时立即返回,做其他事情。 - 复杂的锁操作:如需要锁降级、锁的跨方法获取与释放(但需格外小心)。
java
// 示例:ReentrantLock 带超时和条件变量
ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
public void put(Object item) throws InterruptedException {
lock.lockInterruptibly();
try {
while (isFull()) {
notFull.await();
}
// 添加元素
notEmpty.signal();
} finally {
lock.unlock();
}
}
三、总结建议
- 优先使用
synchronized:除非真的需要ReentrantLock提供的高级特性,否则synchronized更简单、安全、不易出错。现代 JVM 对其优化非常好。 - 使用
ReentrantLock当遇到以下情况:需要可中断 / 超时的锁获取、公平锁、多个条件等待集合、尝试锁获取。 - 注意性能 :在高度竞争的锁场景下,
ReentrantLock通常表现更稳定;但在低竞争或临界区极短时,synchronized可能由于偏向锁更快。实际开发中应基于需求选择,避免过早优化。