在Java中,ReentrantLock
和synchronized
都是用于实现线程同步的机制,但它们在功能、性能和使用场景上存在一些差异。以下是对两者的详细对比和示例代码:
一、核心区别
特性 | synchronized | ReentrantLock |
---|---|---|
锁实现机制 | JVM内置锁,通过对象头Mark Word实现 | JDK层面的API(java.util.concurrent ) |
锁获取方式 | 隐式获取/释放(代码块/方法结束自动释放) | 显式获取/释放(lock() 和unlock() ) |
可重入性 | 支持(同一线程可重复获取同一把锁) | 支持(需手动释放相同次数) |
公平性 | 非公平锁(无法保证获取顺序) | 可配置公平/非公平(通过构造函数指定) |
锁中断 | 不可中断(除非抛出异常) | 可中断(lockInterruptibly() 方法) |
超时机制 | 不支持 | 支持(tryLock(long timeout, TimeUnit unit) ) |
条件变量 | 单一条件(wait() /notify() ) | 可绑定多个条件(newCondition() ) |
性能(高并发) | JDK 6+后优化显著,轻量级场景更优 | 复杂场景(如锁中断、超时)性能更优 |
用法 | 修饰普通方法、静态方法和代码块 | 只能用在代码块 |
二、代码示例
1. synchronized关键字
public class SynchronizedExample {
private int count = 0;
// 同步方法(锁对象为this)
public synchronized void increment() {
count++;
}
// 同步代码块(锁对象为obj)
public void incrementWithBlock() {
Object obj = new Object();
synchronized (obj) {
count++;
}
}
}
2. ReentrantLock类
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private int count = 0;
private final Lock lock = new ReentrantLock(); // 默认非公平锁
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 必须在finally中释放锁
}
}
// 公平锁示例
private final Lock fairLock = new ReentrantLock(true);
// 可中断锁示例
public void doWork() throws InterruptedException {
fairLock.lockInterruptibly();
try {
// 执行任务
} finally {
fairLock.unlock();
}
}
}
3. 条件变量对比
// synchronized + wait/notify
public class SyncConditionExample {
private final Object lock = new Object();
private boolean condition = false;
public void await() throws InterruptedException {
synchronized (lock) {
while (!condition) {
lock.wait(); // 等待条件满足
}
}
}
public void signal() {
synchronized (lock) {
condition = true;
lock.notifyAll(); // 唤醒等待线程
}
}
}
// ReentrantLock + Condition
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockConditionExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean ready = false;
public void await() throws InterruptedException {
lock.lock();
try {
while (!ready) {
condition.await(); // 等待条件
}
} finally {
lock.unlock();
}
}
public void signal() {
lock.lock();
try {
ready = true;
condition.signalAll(); // 唤醒等待线程
} finally {
lock.unlock();
}
}
}
三、适用场景选择
优先使用
synchronized
的场景- 代码简单,不需要高级锁特性(如中断、超时)。
- 锁竞争不激烈,轻量级同步场景。
- 示例:简单的计数器、单例模式的双重检查锁。
优先使用
ReentrantLock
的场景- 需要可中断锁、公平锁或超时机制。
- 需要绑定多个条件变量(如生产者-消费者模型)。
- 锁竞争激烈,需优化性能(如读写锁分离)。
- 示例:高性能缓存的并发控制、分布式系统的本地锁。
四、性能对比
- JDK 6之前:
ReentrantLock
性能明显优于synchronized
。 - JDK 6+:
synchronized
通过锁升级(偏向锁→轻量级锁→重量级锁)优化,性能与ReentrantLock
接近。 - 极端高并发:
ReentrantLock
的可伸缩性更好(如使用公平锁减少线程饥饿)。
五、注意事项
ReentrantLock
必须手动释放锁lock.lock(); try { // 业务逻辑 } finally { lock.unlock(); // 避免死锁 }
公平锁的性能代价
- 公平锁会降低吞吐量(约20%-30%),仅在需要严格顺序时使用。
synchronized
的优化- 锁粗化(Lock Coarsening):将多个连续的锁操作合并为一个。
- 锁消除(Lock Elimination):去除不可能存在竞争的锁。
六、总结
- 简单场景:优先使用
synchronized
(简洁、自动释放)。 - 高级特性:使用
ReentrantLock
(可中断、公平锁、多条件变量)。 - 性能敏感:在高竞争场景下测试两者性能差异后选择。
大多数情况下,synchronized
已能满足需求,仅在需要高级特性时才考虑ReentrantLock
。