当前位置: 首页 > backend >正文

synchronized和RentrantLock用哪个?

在现代 Java 版本(JDK 8 及以后),synchronizedReentrantLock 在大多数常规场景下的性能已经非常接近,甚至在某些情况下 synchronized 表现更好。

因为 synchronized 在 JDK 6 之后引入了偏向锁、轻量级锁和自适应自旋等优化,才使得它的性能得到了质的飞跃,不再是过去那个“重量级”的代名词。


我们来深入探讨一下:

1. synchronized 的演进与优化

在 JDK 6 之前,synchronized 被认为是重量级锁,因为它依赖于操作系统的互斥量(Mutex),涉及到用户态和内核态的切换,开销较大。

但从 JDK 6 开始,JVM 对 synchronized 进行了大量的优化,引入了锁升级机制:

  • 无锁状态: 初始状态,没有竞争。

  • 偏向锁 (Biased Locking):

    • 思想: 如果一个线程反复进入同一个同步块,那么它就不需要每次都进行加锁和解锁的操作。JVM 会“偏向”这个线程,认为它会一直持有这个锁。
    • 原理: 当一个线程第一次获取锁时,JVM 会把锁对象的 Mark Word(对象头的一部分)设置为偏向模式,并记录下这个线程的ID。如果后续还是这个线程来获取锁,它只需要检查Mark Word中的线程ID是否是自己,如果是,就直接进入同步块,无需任何同步操作。
    • 适用场景: 几乎没有竞争的场景。
    • 效率: 极高,几乎没有开销。
  • 轻量级锁 (Lightweight Locking):

    • 思想: 如果有少量线程在短时间内交替竞争同一个锁,但没有发生线程阻塞(即没有线程进入等待状态),那么就不需要升级到重量级锁。
    • 原理: 当偏向锁失效(有其他线程尝试获取锁)时,JVM 会在当前线程的栈帧中创建锁记录(Lock Record),并将锁对象的Mark Word复制到锁记录中,然后尝试使用 CAS (Compare-And-Swap) 操作将Mark Word更新为指向锁记录的指针。如果成功,则获取锁。如果失败,说明有竞争,但如果竞争不激烈,会尝试自旋。
    • 适用场景: 线程交替执行同步块,竞争不激烈。
    • 效率: 较高,避免了用户态/内核态切换。
  • 自适应自旋 (Adaptive Spinning):

    • 思想: 在轻量级锁竞争失败后,线程不会立即阻塞,而是会“自旋”一段时间(空循环),看看持有锁的线程是否很快释放锁。如果自旋成功(锁很快被释放),就避免了线程的上下文切换。自旋的次数不是固定的,而是根据上次自旋的成功率和锁持有者的状态动态调整。
    • 适用场景: 竞争不激烈,锁持有时间短。
    • 效率: 进一步提升轻量级锁的性能。
  • 重量级锁 (Heavyweight Locking):

    • 思想: 如果竞争非常激烈,自旋也无法获取到锁,或者持有锁的线程执行时间过长,导致其他线程长时间自旋,那么就会升级为重量级锁。
    • 原理: 依赖操作系统的互斥量,线程会进入阻塞状态,涉及到用户态和内核态的切换。
    • 适用场景: 竞争激烈,线程需要阻塞等待。
    • 效率: 最低,但能保证正确性。

2. ReentrantLock

ReentrantLockjava.util.concurrent.locks 包下的一个类,它是基于 AQS (AbstractQueuedSynchronizer) 实现的。

核心思想: “我给你提供更灵活的锁操作,你可以手动控制加锁和解锁,还可以尝试非阻塞地获取锁,或者设置超时时间。”

特点:

  • 手动加锁/解锁: 必须手动调用 lock()unlock() 方法,容易忘记释放锁导致死锁。
  • 可中断锁: lockInterruptibly() 方法允许在等待锁的过程中被中断。
  • 尝试非阻塞获取锁: tryLock() 方法可以尝试获取锁,如果失败立即返回,不会阻塞。
  • 公平锁/非公平锁: 可以选择公平锁(按请求顺序获取锁)或非公平锁(抢占式获取锁,默认非公平,性能更高)。
  • 条件变量: 可以通过 newCondition() 创建多个条件变量,实现更复杂的线程间协作。

效率:

  • ReentrantLock 内部也是通过 CAS 操作来实现锁的获取和释放,避免了用户态/内核态的切换(除非竞争激烈到需要阻塞)。
  • 在竞争不激烈的情况下,它的性能与 synchronized 的轻量级锁类似。
  • 在竞争激烈的情况下,它会通过 AQS 的队列机制来管理等待线程,性能也很好。

3. 性能对比与选择

特性/锁类型synchronizedReentrantLock
实现方式JVM 原生支持,字节码指令(monitorenter/exit)Java API,基于 AQS (AbstractQueuedSynchronizer)
锁升级自动升级/降级(偏向 -> 轻量 -> 重量)无锁升级概念,直接使用 CAS 和 AQS 队列
加锁/解锁自动(编译器插入)手动(lock() / unlock()),必须在 finally 块中释放
灵活性较低,功能固定较高,提供更多高级功能(可中断、尝试锁、公平/非公平、条件变量)
公平性非公平可选公平/非公平
性能JDK 8+ 大多数场景下与 ReentrantLock 相当,甚至更好在特定高级功能需求下有优势,常规场景与 synchronized 相当
可重入性
异常处理自动释放锁必须在 finally 块中手动释放,否则可能死锁

为什么说 synchronized 在某些情况下可能更好?

  1. JVM 优化: synchronized 是 JVM 原生支持的,JVM 可以对其进行更深层次的优化,例如逃逸分析、锁消除、锁粗化等。这些优化是 ReentrantLock 无法享受到的。
  2. 自动管理: synchronized 的自动加锁和解锁机制,使得代码更简洁,不易出错。而 ReentrantLock 需要手动管理,一旦忘记 unlock(),就可能导致死锁。
  3. 偏向锁的优势: 在几乎没有竞争的场景下,synchronized 的偏向锁性能是最高的,因为它几乎没有开销。而 ReentrantLock 即使在无竞争情况下,也需要执行 CAS 操作。

什么时候选择 ReentrantLock

尽管 synchronized 性能已大幅提升,但 ReentrantLock 仍然有其不可替代的优势:

  • 需要尝试非阻塞地获取锁: tryLock()
  • 需要可中断地获取锁: lockInterruptibly()
  • 需要设置获取锁的超时时间: tryLock(long timeout, TimeUnit unit)
  • 需要实现公平锁: new ReentrantLock(true)
  • 需要多个条件变量进行线程协作: newCondition()

对于大多数简单的同步需求,优先使用 synchronized。它代码简洁,由 JVM 自动管理,并且在现代 JVM 中性能已经非常优秀。

只有当你需要 ReentrantLock 提供的高级功能(如可中断锁、非阻塞获取锁、公平锁、多条件变量)时,才考虑使用 ReentrantLock

这两种锁的演进,体现了计算机科学中一个重要的思想:“针对特定场景进行优化”

  • synchronized 的优化,是从“悲观锁”的重量级实现,通过分析实际运行时的竞争模式(无竞争、少量竞争、激烈竞争),逐步引入了“乐观锁”的思想(偏向锁、轻量级锁的 CAS),以及“自适应”的思想(自适应自旋),从而在不改变语义的前提下,大幅提升了性能。它是一种**“由内而外”**的优化,由 JVM 自动完成。
  • ReentrantLock 则是提供了一种**“由外而内”**的控制能力,它把锁的更多细节暴露给开发者,让开发者可以根据业务需求,更精细地控制锁的行为。
http://www.xdnf.cn/news/17378.html

相关文章:

  • LangChain-Unstructured 基础使用:PDF 与 Markdown 处理解析
  • 深入解析进程创建与终止机制
  • RAG-大模型课程《李宏毅 2025》作业1笔记
  • 算法篇----分治(快排)
  • 赛灵思ZYNQ官方文档UG585自学翻译笔记:General Purpose I/O (GPIO)通用输入 / 输出,LED控制亮灭,按键控制,中断控制
  • 【Mac】MLX:Lora微调工作流
  • 疯狂星期四文案网第34天运营日记
  • 第15届蓝桥杯Scratch图形化省赛中级组2024年8月24日真题
  • C++四种类型转换
  • 决策树技术详解:从理论到Python实战
  • 数据标准化与归一化的区别与应用场景
  • UE蓝图节点Add Impulse和Add Torque in Radians
  • Solana上Launchpad混战:新颖性应被重视
  • [激光原理与应用-201]:光学器件 - 增益晶体 - 概述
  • 大语言模型提示工程与应用:LLMs文本生成与数据标注实践
  • Java基础-TCP通信(多发多收和一发一收)
  • PHP-单引号和双引号(通俗易懂讲解版)
  • MySQL 元数据详细说明
  • AI基础与实践专题:神经网络基础
  • 探索Trae:使用Trae CN爬取 Gitbook 电子书
  • Java 8 特性
  • 网络管理实战
  • 【QT】常⽤控件详解(六)多元素控件 QListWidget Table Widget Tree Widget
  • QT第三讲- 机制、宏、类库模块
  • MBR分区nvme固态硬盘安装win7--非UEFI启动和GPT分区
  • ruoyi关闭shiro校验,任何接口可以直接访问
  • 可直接运行的 Playwright C# 自动化模板
  • 贪心----1.买卖股票的最佳时机
  • 基于 InfluxDB 的服务器性能监控系统实战(二)
  • 代码随想录day59图论9