synchronized和ReentrantLock的区别是什么?各自的使用场景?

1 阅读3分钟

在 Java 并发编程中,synchronizedReentrantLock 都是用于保证线程同步的常用机制,但它们在底层实现、功能特性和适用场景上有明显区别。

一、核心区别

特性synchronizedReentrantLock
实现层级JVM 内置关键字,通过监视器(Monitor)实现JDK 提供的 API 级别的锁(java.util.concurrent.locks 包)
锁的获取与释放自动获取 / 释放(由 JVM 管理,发生异常时 JVM 会自动释放锁)需手动调用 lock()unlock(),通常在 finally 块中释放,否则可能死锁
可重入性支持(同一个线程可多次进入同一把锁的同步块)支持(同一线程可多次调用 lock(),需对应次数 unlock()
可中断获取锁不支持(线程在等待锁时无法被中断,除非中断后抛出 InterruptedException 的某些情况,但通常不够灵活)支持 lockInterruptibly(),等待锁的线程可响应中断
超时获取锁不支持支持 tryLock(long timeout, TimeUnit unit),超时未获取到锁返回 false
公平锁非公平锁(JVM 不保证等待时间最长的线程优先获得锁)默认非公平,但构造时可指定 true 创建公平锁
条件变量每个对象只有一个等待队列(通过 wait() / notify() / notifyAll()支持多个 Condition 对象(newCondition()),可精细控制不同条件下的线程等待与唤醒
性能早期性能较差,经过优化后(偏向锁、轻量级锁等)在大部分场景下与 ReentrantLock 相差不大性能稳定,但在高度竞争下由于需要 CAS 和队列维护,可能略高于 synchronized(现代 JVM 中差距很小)
调试友好在线程 dump 中能清晰显示哪些帧持有锁以及等待锁的线程同样能看到,但需要区分 AbstractQueuedSynchronizer 状态

二、使用场景

1. synchronized 适用场景

  • 简单同步逻辑:只需保护临界区,不需要高级功能(如中断、超时)。
  • 代码简洁优先:利用 JVM 自动管理锁,降低出错概率。
  • 使用 wait/notify 机制:操作简单,一个条件队列足以满足需求。
  • 无锁竞争或低竞争环境:JVM 的偏向锁、轻量级锁优化效果明显。
  • 不易出错的场景:避免手动释放锁可能导致的死锁问题。
// 示例:synchronized 保护共享变量
public synchronized void increment() {
    counter++;
}

2. ReentrantLock 适用场景

  • 需要可中断的锁获取:避免死锁时,可用 lockInterruptibly() 让等待线程响应中断。
  • 需要超时获取锁:使用 tryLock(long timeout, TimeUnit unit) 避免线程永久阻塞。
  • 公平锁需求:某些业务需要按照线程等待顺序获得锁(但公平锁通常降低吞吐量,需权衡)。
  • 多个条件队列:例如生产者-消费者模式,需要分别控制“不满”和“不空”两个条件。
  • 尝试非阻塞获取锁tryLock() 可在获取不到锁时立即返回,做其他事情。
  • 复杂的锁操作:如需要锁降级、锁的跨方法获取与释放(但需格外小心)。
// 示例:ReentrantLock 带超时和条件变量
ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

public void put(Object item) throws InterruptedException {
    lock.lockInterruptibly();
    try {
        while (isFull()) {
            notFull.await();
        }
        // 添加元素
        notEmpty.signal();
    } finally {
        lock.unlock();
    }
}

三、总结建议

  • 优先使用 synchronized:除非真的需要 ReentrantLock 提供的高级特性,否则 synchronized 更简单、安全、不易出错。现代 JVM 对其优化非常好。
  • 使用 ReentrantLock 当遇到以下情况:需要可中断 / 超时的锁获取、公平锁、多个条件等待集合、尝试锁获取。
  • 注意性能:在高度竞争的锁场景下,ReentrantLock 通常表现更稳定;但在低竞争或临界区极短时,synchronized 可能由于偏向锁更快。实际开发中应基于需求选择,避免过早优化。