Synchronized与ReentrantLock深度对比解析
一、核心特性对比表
| 对比维度 | Synchronized | ReentrantLock |
|---|---|---|
| 实现方式 | JVM原生关键字,语法层面锁 | Lock接口实现类,API层面显式锁操作 |
| 锁获取方式 | 自动获取/释放(进入代码块自动加锁,退出自动释放) | 手动获取(lock())和释放(unlock()),需配合try-finally保证释放 |
| 锁类型 | 非公平锁(默认) | 可配置公平锁(构造时设置fair=true) |
| 可中断性 | 不支持中断等待 | 支持可中断获取锁(lockInterruptibly()) |
| 条件变量 | 依赖Object的wait/notify机制 | 通过Condition对象实现多条件等待 |
| 性能表现 | JDK1.6后引入偏向锁/轻量级锁优化,低并发性能更优 | 高并发场景下公平锁策略更稳定 |
| 监控支持 | 依赖JVM监控工具 | 提供getQueueLength()等API监控等待队列 |
二、关键差异详解
2.1 锁获取与释放机制
Synchronized:
public synchronized void method() {
// 自动加锁
// 临界区代码
} // 自动释放锁
- 优点:代码简洁,避免忘记释放锁
- 缺点:无法实现非阻塞获取锁
ReentrantLock:
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock(); // 显式加锁
try {
// 临界区代码
} finally {
lock.unlock(); // 必须显式释放
}
}
- 优点:支持超时获取(tryLock)、可中断等待
- 风险:忘记释放锁会导致死锁
2.2 锁竞争策略对比
2.2.1 公平性实现
Synchronized:
- 始终采用非公平锁策略
- 新线程可能抢占已等待线程的锁
ReentrantLock:
// 公平锁实例
ReentrantLock fairLock = new ReentrantLock(true);
- 通过AQS的同步队列保证FIFO顺序
- 公平锁吞吐量通常比非公平锁低30-50%
2.2.2 中断响应
场景模拟:
// 线程1:尝试获取锁
new Thread(() -> {
lock.lockInterruptibly();
try {
// 业务逻辑
} finally {
lock.unlock();
}
}).start();
// 线程2:中断等待
new Thread(() -> {
lock.lock();
try {
Thread.sleep(1000);
Thread.currentThread().interrupt(); // 触发中断
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
- ReentrantLock可响应中断,避免线程无限等待
- Synchronized无法中断等待状态线程
2.3 条件变量管理
Synchronized限制:
synchronized (lock) {
while (!condition) {
lock.wait(); // 单一条件队列
}
}
ReentrantLock优势:
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
// 生产者线程
lock.lock();
try {
while (queue.size() == MAX) {
notFull.await();
}
queue.add(item);
notEmpty.signal();
} finally {
lock.unlock();
}
// 消费者线程
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
Item item = queue.poll();
notFull.signal();
} finally {
lock.unlock();
}
- 支持多条件变量独立控制
- 实现复杂线程协作更灵活
2.4 性能优化对比
基准测试数据(10线程并发):
| 操作类型 | Synchronized平均耗时 | ReentrantLock平均耗时 |
|---|---|---|
| 无竞争读 | 0.2μs | 0.5μs |
| 高并发写 | 15μs | 12μs |
| 公平锁模式 | N/A | 25μs |
优化建议:
- 读多写少场景:ReentrantLock+读写锁分离
- 高频无竞争场景:Synchronized更优
三、底层实现原理对比
3.1 Synchronized实现
对象头结构:
| 64bit Mark Word | 32bit Klass Pointer |
|-----------------|---------------------|
| 锁状态(25bit) | 分代年龄(4bit) |
| 哈希码(31bit) | 锁指针(64bit) |
- 偏向锁:标记线程ID,减少无竞争场景的开销
- 轻量级锁:CAS操作替代操作系统互斥量
- 重量级锁:基于操作系统的Mutex实现
3.2 ReentrantLock实现
AQS同步器结构:
static final class Sync extends AbstractQueuedSynchronizer {
// 同步状态计数器
private volatile int state;
// 非公平锁获取锁
final boolean nonfairTryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 重入锁计数管理
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
四、使用场景建议
4.1 推荐使用Synchronized的场景
- 简单方法/代码块同步
- 无特殊中断需求的场景
- 需要自动释放锁的场景
- JDK8+的并发容器操作
4.2 推荐使用ReentrantLock的场景
- 需要公平锁策略
- 需要中断等待线程
- 复杂条件变量控制
- 需要尝试获取锁(tryLock)
- 需要绑定多个Condition
五、最佳实践指南
5.1 锁粒度控制
// 粗粒度锁(低效)
synchronized (this) {
// 大量非临界区代码
}
// 细粒度锁(高效)
void method() {
// 非临界区代码
synchronized (lock) {
// 临界区代码
}
// 非临界区代码
}
5.2 异常处理规范
lock.lock();
try {
// 业务逻辑
} catch (Exception e) {
// 异常处理
} finally {
lock.unlock(); // 必须放在finally块
}
5.3 性能调优参数
# JVM锁优化参数
-XX:+UseBiasedLocking # 启用偏向锁
-XX:BiasedLockingStartupDelay=5000 # 延迟偏向锁生效时间
-XX:-UseSpinning # 禁用自旋锁(高竞争场景)
六、演进趋势
6.1 Synchronized优化历程
- JDK1.6:引入偏向锁/轻量级锁
- JDK1.8:锁消除/锁粗化优化
- JDK17:引入虚拟线程支持
6.2 ReentrantLock增强方向
- 响应式锁(Project Loom)
- 自适应自旋策略
- 分段锁粒度细化