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

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

一、核心区别

特性 synchronized ReentrantLock
实现层级 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 的偏向锁、轻量级锁优化效果明显。
  • 不易出错的场景:避免手动释放锁可能导致的死锁问题。
java 复制代码
// 示例:synchronized 保护共享变量
public synchronized void increment() {
    counter++;
}

2. ReentrantLock 适用场景

  • 需要可中断的锁获取 :避免死锁时,可用 lockInterruptibly() 让等待线程响应中断。
  • 需要超时获取锁 :使用 tryLock(long timeout, TimeUnit unit) 避免线程永久阻塞。
  • 公平锁需求:某些业务需要按照线程等待顺序获得锁(但公平锁通常降低吞吐量,需权衡)。
  • 多个条件队列:例如生产者-消费者模式,需要分别控制"不满"和"不空"两个条件。
  • 尝试非阻塞获取锁tryLock() 可在获取不到锁时立即返回,做其他事情。
  • 复杂的锁操作:如需要锁降级、锁的跨方法获取与释放(但需格外小心)。
java 复制代码
// 示例: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 可能由于偏向锁更快。实际开发中应基于需求选择,避免过早优化。
相关推荐
爱勇宝16 小时前
鸿蒙生态的下半场:开发者不只要能开发,还要能赚钱
android·前端·程序员
Yeyu20 小时前
刷新一帧的艺术:invalidate / postInvalidate / postInvalidateOnAnimation全解析
android
Darling噜啦啦21 小时前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
潘潘潘1 天前
Android OTA 升级原理和流程介绍
android
swipe1 天前
正则表达式入门到进阶:从表单校验到手写模板引擎
前端·javascript·面试
神奇小汤圆1 天前
RAG大厂面试题汇总:向量检索、混合检索、Rerank、幻觉处理高频问题
面试
假如让我当三天老蒯1 天前
回归基本功:Map/Set 与 WeakMap/WeakSet 的区别
前端·面试
plainGeekDev1 天前
null 判断 → Kotlin 可空类型
android·java·kotlin
plainGeekDev1 天前
getter/setter → Kotlin 属性
android·java·kotlin
假如让我当三天老蒯1 天前
回归基本功!前端的解构赋值、扩展运算符、剩余参数
前端·面试