#synchronized vs ReentrantLock
#面试中的灵魂拷问
面试官问:"synchronized和ReentrantLock有什么区别?"
候选人小张想了想,说:"synchronized是关键字,ReentrantLock是类。"
面试官追问:"还有呢?"
小张:"synchronized自动释放锁,ReentrantLock要手动释放..."
面试官点点头,继续问:"那你什么时候用synchronized,什么时候用ReentrantLock?"
小张停顿了一下:"呃...大部分时候用synchronized?"
这个问题看似基础,但能回答好的人不多。大部分人只能说出"自动释放 vs 手动释放",但对为什么需要ReentrantLock、各自的适用场景、性能差异理解不深。
今天这篇文章,把两者的区别彻底讲清楚。
#基本概念对比
#synchronized
// 修饰实例方法
public synchronized void method() {
// 锁对象是 this
}
// 修饰静态方法
public static synchronized void staticMethod() {
// 锁对象是 Class 对象
}
// 修饰代码块
public void blockMethod() {
synchronized (this) {
// 锁对象是指定对象
}
}特点:
- Java语言内置的关键字
- JVM层面的实现(monitorenter/monitorexit)
- 自动获取和释放锁
- 锁不能中断、不能超时、不能非阻塞获取
#ReentrantLock
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock(); // 获取锁
try {
// 业务逻辑
} finally {
lock.unlock(); // 必须在finally中释放
}
}
// 非公平锁(默认)
private final ReentrantLock unfairLock = new ReentrantLock();
// 公平锁
private final ReentrantLock fairLock = new ReentrantLock(true);
// 可中断获取
public void interruptibleMethod() throws InterruptedException {
lock.lockInterruptibly();
try {
// 业务逻辑
} finally {
lock.unlock();
}
}
// 尝试获取(不阻塞)
public void tryLockMethod() {
if (lock.tryLock()) { // 立即返回
try {
// 获取成功
} finally {
lock.unlock();
}
} else {
// 获取失败,做其他事
}
}
// 带超时获取
public void tryLockWithTimeout() throws InterruptedException {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 5秒内获取到锁
} finally {
lock.unlock();
}
} else {
// 超时,获取失败
}
}
}#核心区别对比
#功能特性对比
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现层次 | JVM内置 | JDK API |
| 锁获取 | 自动获取 | 手动调用lock() |
| 锁释放 | 自动释放 | 必须finally中unlock() |
| 中断等待 | 不支持 | lockInterruptibly() |
| 超时获取 | 不支持 | tryLock(time) |
| 非阻塞获取 | 不支持 | tryLock() |
| 公平锁 | 非公平 | 可公平/非公平 |
| 多条件等待 | Object.wait/notify | 多个Condition |
| 可重入 | 支持 | 支持 |
| 性能 | JDK 6后优化良好 | 略优于synchronized |
#可见性保证
两者都保证可见性和有序性:
// synchronized
synchronized (lock) {
x = 10; // happens-before unlock
}
// ReentrantLock
lock.lock();
try {
x = 10; // happens-before unlock
} finally {
lock.unlock();
}happens-before链:
- lock() happens-before unlock()
- unlock() happens-before 后续lock()
- 临界区内的操作 happens-before unlock()
#ReentrantLock的高级特性
#公平锁 vs 非公平锁
非公平锁(默认):
ReentrantLock lock = new ReentrantLock(); // 非公平
public void lockMethod() {
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
}获取流程:
- 尝试CAS直接获取锁(插队)
- 如果成功,立即执行
- 如果失败,排队等待
公平锁:
ReentrantLock lock = new ReentrantLock(true); // 公平
public void lockMethod() {
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
}获取流程:
- 检查等待队列是否有更早的线程
- 如果有,乖乖去排队
- 如果没有,尝试获取锁
// 公平锁的实现伪代码
protected final boolean tryAcquire(int acquires) {
// 检查是否有前驱节点
if (hasQueuedPredecessor()) {
return false; // 有更早等待的线程,不获取
}
// 没有更早等待的线程,尝试CAS获取
return compareAndSetState(0, acquires);
}
// 非公平锁的实现伪代码
final void lock() {
// 直接尝试CAS获取,不检查队列
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}#公平 vs 非公平的权衡
| 维度 | 非公平锁 | 公平锁 |
|---|---|---|
| 吞吐量 | 高(可插队) | 低(必须排队) |
| 响应性 | 更快(立即执行) | 可能延迟 |
| 饥饿风险 | 存在 | 不存在 |
| 适用场景 | 高并发场景 | 锁竞争不激烈的场景 |
// 非公平锁可能导致饥饿的例子
public class StarvationDemo {
private final ReentrantLock lock = new ReentrantLock(false);
public void highPriority() {
// 高优先级线程不断插队
while (true) {
if (lock.tryLock()) { // 插队
try {
// 快速执行
} finally {
lock.unlock();
}
}
}
}
public void lowPriority() {
// 低优先级线程可能永远得不到锁
lock.lock();
try {
// 可能永远执行不到
} finally {
lock.unlock();
}
}
}#可中断等待
synchronized不能中断:
// ❌ synchronized无法响应中断
public class SyncInterruptDemo {
private final Object lock = new Object();
public void method() {
synchronized (lock) {
// 阻塞在这个同步块中
// 无法被中断
}
}
}ReentrantLock支持中断:
public class InterruptibleLockDemo {
private final ReentrantLock lock = new ReentrantLock();
public void interruptibleMethod() throws InterruptedException {
lock.lockInterruptibly(); // 可中断获取
try {
// 业务逻辑
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
InterruptibleLockDemo demo = new InterruptibleLockDemo();
Thread t = new Thread(() -> {
try {
demo.interruptibleMethod();
} catch (InterruptedException e) {
System.out.println("被中断了");
}
});
t.start();
// 主线程中断t
t.interrupt();
}
}使用场景:
- 任务可以被取消的场景
- 限时任务
- 优雅关闭
#超时获取
synchronized不能超时:
// ❌ 只能无限等待
public synchronized void blockingMethod() {
// 如果拿不到锁,永远阻塞
}ReentrantLock支持超时:
public class TryLockDemo {
private final ReentrantLock lock = new ReentrantLock();
public void tryLockMethod() {
try {
// 尝试获取锁,最多等待5秒
boolean acquired = lock.tryLock(5, TimeUnit.SECONDS);
if (acquired) {
try {
// 获取成功
} finally {
lock.unlock();
}
} else {
// 超时,获取失败
System.out.println("获取锁超时");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}实际应用:
public class OrderService {
private final ReentrantLock lock = new ReentrantLock();
private final Map<String, Integer> orders = new HashMap<>();
public boolean tryCreateOrder(String orderId, int amount) {
try {
// 最多等待1秒获取锁
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
if (orders.containsKey(orderId)) {
return false; // 订单已存在
}
orders.put(orderId, amount);
return true;
} finally {
lock.unlock();
}
} else {
// 超时,可能是死锁或系统繁忙
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}#多个Condition
Object.wait/notify只能有一个条件队列:
public class SingleConditionDemo {
private final Object lock = new Object();
private boolean resourceAvailable = false;
private boolean processingDone = false;
// ❌ 只能用一个wait set
public synchronized void await() throws InterruptedException {
while (!resourceAvailable) {
wait(); // 只能等待resourceAvailable
}
}
public synchronized void notifyResource() {
resourceAvailable = true;
notify(); // 可能唤醒错误的线程
}
}ReentrantLock支持多个Condition:
import java.util.concurrent.locks.Condition;
public class MultipleConditionDemo {
private final ReentrantLock lock = new ReentrantLock();
private final Condition resourceCondition = lock.newCondition();
private final Condition processingCondition = lock.newCondition();
private boolean resourceAvailable = false;
private boolean processingDone = false;
public void awaitResource() throws InterruptedException {
lock.lock();
try {
while (!resourceAvailable) {
resourceCondition.await(); // 只等待资源
}
} finally {
lock.unlock();
}
}
public void awaitProcessing() throws InterruptedException {
lock.lock();
try {
while (!processingDone) {
processingCondition.await(); // 只等待处理完成
}
} finally {
lock.unlock();
}
}
public void signalResource() {
lock.lock();
try {
resourceAvailable = true;
resourceCondition.signal(); // 只唤醒等待资源的线程
} finally {
lock.unlock();
}
}
}生产者-消费者模式:
public class BoundedBuffer<T> {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Object[] items;
private int count = 0;
private int putPtr = 0;
private int takePtr = 0;
public BoundedBuffer(int capacity) {
items = new Object[capacity];
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 缓冲区满,等待
}
items[putPtr] = item;
if (++putPtr == items.length) putPtr = 0;
count++;
notEmpty.signal(); // 唤醒消费者
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 缓冲区空,等待
}
@SuppressWarnings("unchecked")
T item = (T) items[takePtr];
if (++takePtr == items.length) takePtr = 0;
count--;
notFull.signal(); // 唤醒生产者
return item;
} finally {
lock.unlock();
}
}
}#生产场景选型
#默认选择synchronized
// ✅ 简单场景用synchronized
public class SimpleCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}适用synchronized的场景:
- 代码简单,逻辑不复杂
- 不需要超时、中断、多条件
- 锁竞争不激烈
- 自动释放更安全
#选择ReentrantLock的场景
// ✅ 需要公平锁
private final ReentrantLock fairLock = new ReentrantLock(true);
// ✅ 需要超时
public boolean tryLockWithTimeout() {
try {
return lock.tryLock(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return false;
}
}
// ✅ 需要可中断
public void interruptiblyLock() throws InterruptedException {
lock.lockInterruptibly();
try {
// 业务逻辑
} finally {
lock.unlock();
}
}
// ✅ 需要多个条件队列
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();适用ReentrantLock的场景:
- 需要公平锁保证
- 需要超时控制
- 需要可中断等待
- 需要多个等待条件
- 需要tryLock()非阻塞获取
#实际案例:限流器
public class RateLimiter {
private final ReentrantLock lock = new ReentrantLock();
private final long timeout;
private final int maxPermits;
private long permits;
private long lastUpdateTime;
public RateLimiter(int maxPermits, long timeout, TimeUnit unit) {
this.maxPermits = maxPermits;
this.timeout = unit.toMillis(timeout);
this.permits = maxPermits;
this.lastUpdateTime = System.currentTimeMillis();
}
public boolean tryAcquire() {
lock.lock();
try {
long now = System.currentTimeMillis();
// 补充令牌
permits = Math.min(maxPermits,
permits + (now - lastUpdateTime) / timeout);
lastUpdateTime = now;
if (permits >= 1) {
permits--;
return true;
}
return false;
} finally {
lock.unlock();
}
}
public boolean acquire(long time, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(time);
lock.lockInterruptibly();
try {
long now = System.currentTimeMillis();
permits = Math.min(maxPermits,
permits + (now - lastUpdateTime) / timeout);
lastUpdateTime = now;
long deadline = System.nanoTime() + nanos;
while (permits < 1) {
if (nanos <= 0) return false;
nanos = notFull.awaitNanos(nanos);
}
permits--;
return true;
} finally {
lock.unlock();
}
}
}#性能对比
#JDK 6之后的优化
JDK 6对synchronized进行了大量优化:
- 偏向锁
- 轻量级锁
- 锁消除
- 锁粗化
- 自适应自旋
现在synchronized和ReentrantLock性能差异很小。
#benchmark测试
public class LockPerformanceTest {
private final Object syncLock = new Object();
private final ReentrantLock reentrantLock = new ReentrantLock();
@Benchmark
public void synchronizedBenchmark() {
for (int i = 0; i < 1000; i++) {
synchronized (syncLock) {
// 空操作
}
}
}
@Benchmark
public void reentrantLockBenchmark() {
for (int i = 0; i < 1000; i++) {
reentrantLock.lock();
try {
// 空操作
} finally {
reentrantLock.unlock();
}
}
}
}
// 结果(JDK 17,无竞争情况):
// synchronized: ~100ns per lock
// ReentrantLock: ~150ns per lock
// 差异主要是手动lock/unlock的开销#常见误区
#误区1:ReentrantLock一定比synchronized快
// ❌ 错误:在无竞争情况下
public void wrongOptimization() {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
// 这里比synchronized多了try-finally的开销
}
// ✅ 正确:简单场景用synchronized
public void simpleSynchronized() {
synchronized (this) {
// 业务逻辑
}
}#误区2:忘记在finally中释放锁
// ❌ 错误:异常时锁无法释放
public void wrongLock() {
reentrantLock.lock();
if (condition) {
return; // 锁泄漏!
}
reentrantLock.unlock();
}
// ✅ 正确:必须在finally中释放
public void correctLock() {
reentrantLock.lock();
try {
if (condition) {
return;
}
} finally {
reentrantLock.unlock();
}
}#误区3:混合使用synchronized和ReentrantLock
// ❌ 错误:混用导致死锁
private final Object syncLock = new Object();
private final ReentrantLock reentrantLock = new ReentrantLock();
public void deadlockMethod() {
synchronized (syncLock) {
reentrantLock.lock(); // 可能死锁
try {
// 业务逻辑
} finally {
reentrantLock.unlock();
}
}
}
// ✅ 正确:统一使用同一种锁
private final ReentrantLock lock = new ReentrantLock();#面试中的高频追问
#追问1:ReentrantLock是如何实现可重入的?
通过AQS的state计数器实现:
- 第一次获取锁:state从0变为1
- 同一线程再次获取:state加1
- 释放锁:state减1
- state变为0时,完全释放锁
#追问2:synchronized的锁能实现公平吗?
不能。synchronized只有非公平锁。JDK 6之前没有偏向锁/轻量级锁优化时,性能很差。
#追问3:lockInterruptibly()和tryLock()有什么区别?
lockInterruptibly():阻塞等待,但可以被中断tryLock():立即返回,不阻塞(获取成功/失败)tryLock(timeout):等待指定时间,可以被中断
#【学习小结】
- 默认用synchronized:简单场景、不需要高级特性时
- 用ReentrantLock:需要公平锁、超时、中断、多个条件
- synchronized优势:语法简洁、自动释放、不易出错
- ReentrantLock优势:功能丰富、可控制性强
- 性能差异不大:JDK 6后synchronized优化良好
- 必须配合try-finally:手动释放锁必须放在finally中
- 禁止混用:同一逻辑不能同时用两种锁
延伸阅读: