synchronized 和 ReentrantLock 都是 Java 中用于实现线程同步的机制,用于解决多线程并发访问共享资源时的线程安全问题。它们有相似之处,但也存在显著差异,以下是详细对比:
1. 基本概念
-
synchronized:- Java 内置的关键字,属于 JVM 层面的锁机制。
- 基于 JVM 隐式实现,无需手动释放锁,由编译器自动处理加锁和释放(如方法结束或异常时自动释放)。
- 可修饰方法(实例方法、静态方法)或代码块。
-
ReentrantLock:- Java 1.5 引入的
java.util.concurrent.locks包中的类,属于 API 层面的锁机制。 - 需要显式通过
lock()加锁和unlock()释放锁,通常配合try-finally确保锁释放。 - 实现了
Lock接口,支持更多灵活的功能。
- Java 1.5 引入的
2. 核心区别
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 锁的实现 | JVM 内置关键字(C++ 实现) | API 层面的类(Java 代码实现) |
| 锁的释放 | 自动释放(方法结束 / 异常) | 必须手动调用 unlock()(通常在 finally 中) |
| 可中断性 | 不可中断,一旦阻塞无法被中断 | 可中断(lockInterruptibly() 方法) |
| 公平锁支持 | 非公平锁(无法设置为公平锁) | 支持公平锁和非公平锁(构造函数参数指定) |
| 尝试获取锁 | 不支持(获取不到则阻塞) | 支持 tryLock() 尝试获取锁(可超时) |
| 条件变量 | 不支持(仅通过 wait()/notify()) | 支持 Condition,可创建多个条件变量 |
| 锁的状态查询 | 无法直接查询 | 可通过 isLocked() 等方法查询 |
| 性能 | JDK 1.6 后优化(偏向锁、轻量级锁),性能接近 ReentrantLock | 早期性能优于 synchronized,现在差异不大 |
3. 关键特性详解
(1)可中断性
-
synchronized:一旦线程进入阻塞状态(等待锁),无法被中断,只能一直等待。 -
ReentrantLock:提供lockInterruptibly()方法,允许线程在等待锁时响应中断,避免无限期阻塞。java
运行
// ReentrantLock 可中断示例 ReentrantLock lock = new ReentrantLock(); try { lock.lockInterruptibly(); // 可被中断的加锁 // 临界区代码 } catch (InterruptedException e) { // 处理中断 } finally { lock.unlock(); }
(2)公平锁
-
公平锁:线程获取锁的顺序与请求顺序一致,避免饥饿(长期等待的线程有机会获取锁)。
-
非公平锁:允许 “插队”,可能导致某些线程长期无法获取锁,但性能通常更好。
synchronized:仅支持非公平锁。ReentrantLock:默认非公平锁,可通过构造函数new ReentrantLock(true)实现公平锁。
(3)尝试获取锁
-
ReentrantLock的tryLock()方法可尝试获取锁,成功返回true,失败返回false,无需阻塞:java
运行
if (lock.tryLock(1, TimeUnit.SECONDS)) { // 超时等待1秒 try { // 临界区 } finally { lock.unlock(); } } else { // 获取锁失败的处理逻辑 } -
synchronized无此功能,获取不到锁时会直接阻塞。
(4)条件变量(Condition)
-
ReentrantLock可通过newCondition()创建多个Condition对象,实现更灵活的线程通信:java
运行
ReentrantLock lock = new ReentrantLock(); Condition notEmpty = lock.newCondition(); Condition notFull = lock.newCondition(); // 线程1等待条件 lock.lock(); try { while (queue.isEmpty()) { notEmpty.await(); // 等待“非空”信号 } } finally { lock.unlock(); } // 线程2发送信号 lock.lock(); try { queue.add(element); notEmpty.signal(); // 通知“非空”条件已满足 } finally { lock.unlock(); } -
synchronized仅通过对象的wait()/notify()/notifyAll()实现通信,且一个对象只能有一组条件。
4. 选择建议
-
优先使用
synchronized:- 代码更简洁,无需手动管理锁的释放,降低出错风险。
- 适合简单的同步场景(如单条件的线程通信)。
- JVM 对其优化持续升级(如偏向锁、轻量级锁)。
-
使用
ReentrantLock场景:- 需要中断等待锁的线程(
lockInterruptibly())。 - 需要公平锁机制。
- 需要尝试获取锁(
tryLock())或超时等待。 - 需要多个条件变量(
Condition)。 - 需要查询锁的状态(如监控锁的持有情况)。
- 需要中断等待锁的线程(
总结
synchronized 是 Java 原生的简单同步机制,适合大多数基础场景;ReentrantLock 提供更丰富的功能,灵活性更高,适合复杂的同步需求。实际开发中需根据具体场景选择,两者的核心目的都是保证线程安全,但特性和使用方式差异较大。