ReentrantLock 和 synchronized 如何选择?

4 阅读4分钟

可以先记一个结论:

简单同步,优先 synchronized;需要高级锁能力,选 ReentrantLock


一、先说本质区别

synchronized

这是 Java 内置的监视器锁,写法简单,语法级支持:

synchronized (lock) {
    // 临界区
}

或者修饰方法:

public synchronized void test() {
}

特点是:

  • 写法最简单
  • JVM 层面支持,出了作用域会自动释放锁
  • 可重入
  • 从 JDK 1.6 以后做了很多优化,性能已经很好
  • 不能灵活中断、超时、轮询获取锁

ReentrantLock

这是 java.util.concurrent.locks 包里的显式锁:

Lock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区
} finally {
    lock.unlock();
}

特点是:

  • 也是可重入锁

  • 需要手动释放锁,必须放在 finally

  • 功能更强,支持:

    • tryLock():尝试获取锁
    • tryLock(timeout):超时等待
    • lockInterruptibly():可中断获取锁
    • 可选公平锁
    • Condition 多条件队列

二、怎么选:直接按场景判断

1. 大多数业务代码:优先 synchronized

如果你只是做这种事情:

  • 方法加锁
  • 代码块互斥
  • 不需要超时、中断、公平性
  • 逻辑比较简单

那就优先用 synchronized

因为它:

  • 代码短
  • 不容易忘记释放锁
  • 可读性更好
  • 出错概率更低

例如:

public synchronized void add() {
    count++;
}

或者:

private final Object lock = new Object();

public void add() {
    synchronized (lock) {
        count++;
    }
}

这类场景完全够用。


2. 需要更强控制能力:选 ReentrantLock

当你有下面这些需求时,synchronized 就不够用了:

① 需要尝试获取锁,拿不到就放弃

比如避免线程一直阻塞:

if (lock.tryLock()) {
    try {
        // 获取到锁再执行
    } finally {
        lock.unlock();
    }
} else {
    // 获取失败,走降级逻辑
}

synchronized 做不到。


② 需要可中断地等待锁

比如线程在排队等锁时,希望能响应中断:

lock.lockInterruptibly();
try {
    // 业务逻辑
} finally {
    lock.unlock();
}

synchronized 在等待锁时不能响应中断。


③ 需要超时机制

比如最多等 2 秒,还拿不到锁就返回失败:

if (lock.tryLock(2, TimeUnit.SECONDS)) {
    try {
        // 处理业务
    } finally {
        lock.unlock();
    }
} else {
    // 超时处理
}

synchronized 也做不到。


④ 需要多个条件队列

ReentrantLock 可以配合多个 Condition,做更精细的线程唤醒控制。

例如生产者消费者,不同线程等待不同条件:

Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

synchronized 只有一个隐式条件队列,只能配 wait/notify/notifyAll,控制粒度较粗。


⑤ 需要公平锁

Lock fairLock = new ReentrantLock(true);

公平锁会尽量按等待顺序获取锁。

不过要注意:

  • 公平锁吞吐量通常更低
  • 不是所有场景都值得用

三、性能上怎么理解

很多人以前会说:ReentrantLocksynchronized 性能好。

这个结论放到现在已经不准确了。

从 JDK 1.6 开始,synchronized 有了很多优化,比如:

  • 偏向锁(历史上)
  • 轻量级锁
  • 锁粗化
  • 锁消除
  • 自适应自旋

所以在现代 JDK 中:

  • 低竞争或普通竞争场景下,synchronizedReentrantLock 性能差距通常没那么大
  • 高并发复杂场景下,ReentrantLock 因为功能更多,有时更容易调优
  • 真正决定性能的往往不是“用哪把锁”,而是锁粒度、竞争程度、临界区大小

所以不要为了“可能快一点”就默认上 ReentrantLock


四、可维护性上谁更好

通常是 synchronized 更安全、更容易维护

因为:

synchronized

  • 自动释放锁
  • 少写模板代码
  • 不容易遗漏

ReentrantLock

  • 必须手动 unlock()
  • 一旦忘记释放,容易死锁

错误示例:

lock.lock();
doSomething();
if (error) {
    return;   // 忘记 unlock
}
lock.unlock();

正确示例:

lock.lock();
try {
    doSomething();
} finally {
    lock.unlock();
}

所以如果不需要高级能力,优先 synchronized 更稳。


五、面试里常见回答模板

synchronizedReentrantLock 都是可重入的互斥锁。
一般情况下,如果只是做简单的同步控制,我会优先选择 synchronized,因为语法简单,JVM 原生支持,自动释放锁,可读性和安全性更好。
如果业务需要更灵活的锁控制,比如可中断获取锁、超时获取锁、尝试加锁、公平锁,或者配合多个 Condition 做精细化线程协作,我会选择 ReentrantLock
在现代 JDK 中,两者性能差距通常不是首要考虑因素,更关键的是锁竞争程度、锁粒度和临界区设计。


六、一句话记忆

能用 synchronized 解决,就先用 synchronized
只有当你明确需要 ReentrantLock 的高级特性时,再用 ReentrantLock


七、给你一个选择表

synchronized

  • 临界区简单
  • 只需要互斥
  • 想降低代码复杂度
  • 更看重稳定和可维护性

ReentrantLock

  • 需要 tryLock
  • 需要超时等待
  • 需要可中断等待
  • 需要公平锁
  • 需要多个 Condition