ReentrantLock 与 synchronized 异同点对比
写在开头
ReentrantLock 是一种独占式的可重入锁,位于 java.util.concurrent.locks 中,是 Lock 接口的默认实现类,底部的同步特性基于 AQS 实现,和 synchronized 关键字类似,但更灵活、功能更强大。synchronized 是 Java 内置的同步关键字,也是可重入锁。
本文对两者的异同点进行全面的总结。
一、相同点
1. 目的相同
两者都是用于实现线程同步的机制,避免多个线程同时访问共享资源时导致的数据竞争问题,从而保证线程安全。
2. 都是独占锁
在多线程环境下,两者都可以保证在同一时刻只有一个线程能够执行被锁定的代码块或方法。
3. 都是可重入锁
同一个线程可以重复获得同一个锁。synchronized 自动支持重入,ReentrantLock 通过内部计数器记录重入次数。
4. 都会自动释放锁
- synchronized:执行完同步代码块或方法后,JVM 自动释放锁。
- ReentrantLock:需要显式调用
unlock()释放,通常放在finally块中。
二、区别
1. 锁的实现层面不同
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现层面 | JVM 层面实现(内置关键字) | JDK API 层面实现(类) |
| 使用方式 | 隐式锁,自动获取和释放 | 显式锁,需手动 lock()/unlock() |
| 语法简洁性 | 更简洁,代码量少 | 需要 try/finally 配合 |
示例对比:
// synchronized
public synchronized void method() {
// 同步代码
}
// ReentrantLock
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 同步代码
} finally {
lock.unlock();
}
2. 功能特性不同
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 可中断等待 | ❌ 不支持(线程一直等待) | ✅ 支持(lockInterruptibly()) |
| 公平锁 | ❌ 非公平锁 | ✅ 可设置公平/非公平 |
| 超时获取 | ❌ 不支持 | ✅ 支持 tryLock(timeout, unit) |
| Condition 等待/通知 | ❌ 只有单一的 wait/notify | ✅ 支持多个 Condition 实例 |
| 锁的灵活性 | 固定 | 灵活,支持多种高级功能 |
3. 锁的公平性
- synchronized:非公平锁,无法保证线程获取锁的顺序。
- ReentrantLock:默认非公平锁,但可通过构造函数
new ReentrantLock(true)设置为公平锁。公平锁按申请顺序获取锁,但性能开销更大。
4. 可中断性
- synchronized:线程等待锁时不可被中断,只能一直等待。
- ReentrantLock:使用
lockInterruptibly()可使等待锁的线程响应中断,适合需要取消任务处理的场景。
5. 条件变量(Condition)
- synchronized:使用
wait()、notify()、notifyAll()实现线程间通信,只能有一个等待队列。 - ReentrantLock:通过
newCondition()可创建多个 Condition 对象,实现精准唤醒特定线程。例如生产者-消费者模式中,可以分别创建"满"和"空"的条件变量。
Condition 示例:
ReentrantLock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();
// 生产者
lock.lock();
try {
while (队列满) {
notFull.await();
}
生产数据();
notEmpty.signal();
} finally {
lock.unlock();
}
// 消费者
lock.lock();
try {
while (队列空) {
notEmpty.await();
}
消费数据();
notFull.signal();
} finally {
lock.unlock();
}
6. 性能对比
- 低竞争场景:两者性能相近,synchronized 经过 JDK 1.6 优化(偏向锁、轻量级锁)后开销很小。
- 高竞争场景:ReentrantLock 通常优于 synchronized,因为 synchronized 在高竞争下锁升级为重量级锁,阻塞/唤醒开销较大。
- 测试数据(10 线程,各执行 100 万次递增):
- ReentrantLock:约 272 ms
- synchronized:约 675 ms
7. 可重入实现机制
- synchronized:基于 JVM 机制,无需开发者关心。
- ReentrantLock:基于 AQS(AbstractQueuedSynchronizer),使用内部的
state变量充当计数器:state == 0:锁空闲- 每次
lock()通过 CAS 将 state +1 - 每次
unlock()将 state -1 - state 归 0 时锁才真正释放
8. 分布式系统适用性
- 两者都只能作用于单个 JVM 实例,无法跨 JVM 同步。
- 分布式环境需使用 Redis、ZooKeeper 等分布式锁方案。
9. 可维护性
- synchronized:代码简洁,不易出错,适合简单同步场景。
- ReentrantLock:代码结构较复杂,需确保所有路径都释放锁,但灵活性高,适合复杂业务场景。
三、总结
| 对比维度 | synchronized | ReentrantLock |
|---|---|---|
| 关键字/类 | 关键字 | 类 |
| 显式/隐式 | 隐式 | 显式 |
| 锁释放 | 自动 | 手动(finally) |
| 可重入 | 是 | 是 |
| 公平锁 | 否 | 可选 |
| 可中断 | 否 | 是 |
| 超时 | 否 | 是 |
| Condition | 单一 | 多个 |
| 底层实现 | JVM | AQS(CAS) |
| 性能(高竞争) | 较差 | 较好 |
| 代码简洁性 | 高 | 中 |
使用建议:
- 简单同步场景优先使用
synchronized,简洁安全。 - 需要公平锁、可中断、超时、多条件等高级特性时,使用
ReentrantLock。