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

【Java EE】锁策略、锁升级、锁消除和锁粗化

锁策略、锁升级、锁消除和锁粗化

  • 锁策略
    • 悲观锁 vs 乐观锁
    • 公平锁 vs 非公平锁
    • 可重入锁 vs 不可重入锁
      • 可重入锁的完整实现逻辑
    • 自旋锁 vs 挂起等待锁
    • 互斥锁 vs 读写锁
    • 轻量级锁 vs 重量级锁
      • 两种锁的工作流程
    • 总结⭐
  • 锁升级
    • synchronized 锁自动升级路径⭐
      • 对象头与锁状态
      • 无锁 → 偏向锁(Biased Locking)
      • 偏向锁 → 轻量级锁
      • 轻量级锁→ 重量级锁
  • 锁消除
  • 锁粗化

本文将深入理解Java中的常见锁策略,并重点探讨JVM层面的三大优化手段:锁升级(Lock Escalation)、锁消除(Lock Elimination)与锁粗化(Lock Coarsening)

锁策略

从不同的维度看,锁可以分为多种类型:

悲观锁 vs 乐观锁

  • 悲观锁:总是假设最坏的情况——每次读写数据,别人都会来修改。所以它会在操作前先加锁,阻塞其他线程。

  • 乐观锁:很天真地认为冲突一般不会发生,所以先不加锁,直接操作。更新时,再检查一下数据有没有被别人动过。如果没被改,就写入;如果被改了,就重试或放弃。

synchronizedReentrantLock都是典型的悲观锁。

适用场景:乐观锁适合读多写少的场景,能减少加锁开销;悲观锁则适合写操作频繁的场景,避免无休止的重试。

公平锁 vs 非公平锁

多线程排队等锁,锁被释放时,该轮到谁?

  • 公平锁:严格遵循先来后到。线程A比B先来排队,A就一定能比B先拿到锁。
  • 非公平锁:不排队。锁一释放,所有等待的线程(甚至刚来的新线程)一起哄抢,谁抢到算谁的。

synchronized就是典型的非公平锁。ReentrantLock则支持通过构造参数自由选择是公平还是非公平。

公平锁虽然看起来更公平,但它进行线程调度和维护等待队列的成本更高。非公平锁性能更好,但可能导致某些线程始终抢不到锁,造成饥饿。

可重入锁 vs 不可重入锁

一个已经拿到锁的线程,还能再拿一次这把锁吗?

  • 可重入锁:允许。同一个线程可以多次获取同一把锁,不会自己把自己锁死。比如一个同步方法里调用另一个同步方法。
  • 不可重入锁:不允许。线程第二次获取锁时会阻塞,直到自己释放,但这永远不可能发生,于是造成死锁

synchronizedReentrantLock都是可重入锁

可重入锁的完整实现逻辑

可视化

自旋锁 vs 挂起等待锁

当线程抢锁失败,是原地等待还是暂时放弃CPU?

  • 自旋锁(Spin Lock):抢锁失败的线程不放弃CPU资源,而是原地死循环,反复尝试获取锁,直到成功。
    优点:一旦锁被释放,自己能瞬间感知并获取,没有线程调度的延迟。
    缺点:如果锁被持有很久,自旋的线程会空耗CPU,造成浪费。

  • 挂起等待锁:线程抢锁失败后,直接进入阻塞状态,让出CPU资源。等锁释放后,系统再重新调度唤醒它。
    优点:不浪费CPU资源,线程阻塞期间CPU可以去做更有意义的事。
    缺点:从阻塞到被唤醒,存在调度延迟。

互斥锁 vs 读写锁

  • 互斥锁是最简单也最严格的锁模式。它的规则只有一条:任何时刻,只能有一个线程持有锁,无论是读还是写。

    线程A(读)🔒 ──────────── 🔓 线程B(读) ⏳等待 🔒 ──── 🔓 线程C(写) ⏳等待 ⏳等待 🔒 ──── 🔓 ───────────────────────────────────────────→ 时间
  • 读写锁(ReadWriteLock):读写锁把读和写区别对待,引入了三种状态:
    无锁状态:没有任何线程持有锁。
    读锁(共享锁):多个线程可以同时持有,彼此不阻塞。
    写锁(独占锁):一次只能有一个线程持有,且与其他所有锁互斥。

    线程A(读)🔒共享 ──────────── 🔓 线程B(读)🔒共享 ──────────── 🔓 线程C(写) ⏳等待 🔒独占 ──── 🔓 ───────────────────────────────────────────→ 时间

实际规则表

当前锁状态申请读锁申请写锁
无锁✅ 获得读锁✅ 获得写锁
已被读锁持有✅ 可重入/共享❌ 阻塞
已被写锁持有❌ 阻塞✅ 仅持有线程可重入

在Java中,核心实现是ReentrantReadWriteLock

ReadWriteLockrwLock=newReentrantReadWriteLock();LockreadLock=rwLock.readLock();// 共享锁LockwriteLock=rwLock.writeLock();// 独占锁// 读操作:多个线程可同时执行publicStringreadData(){readLock.lock();try{returnsharedData;}finally{readLock.unlock();}}// 写操作:独占执行publicvoidwriteData(StringnewVal){writeLock.lock();try{sharedData=newVal;}finally{writeLock.unlock();}}

适用场景读多写少。比如配置缓存、元数据读取等,用读写锁能让大量读线程并发执行,性能远高于互斥锁。

轻量级锁 vs 重量级锁

对比维度轻量级锁重量级锁
等待方式自旋等待(忙等,占用CPU)挂起等待(释放CPU,进入阻塞队列)
实现层级JVM层面,用户态CAS操作操作系统层面,内核态Mutex
适用场景锁持有时间短、竞争不激烈锁持有时间长、竞争激烈
加锁开销小(只是一条CPU原子指令)大(系统调用,用户态↔内核态切换)
等待开销空转消耗CPU不消耗CPU,但线程切换开销大
线程状态线程始终处于RUNNABLE状态线程进入BLOCKED状态
锁记录位置线程栈帧中的 Lock Record堆中对象关联的 ObjectMonitor
Java中的定位synchronized的低竞争优化形态synchronized的最终兜底形态

两种锁的工作流程

可视化

线程切换开销S锁持有时间T

  • 如果T < S:自旋的好处(不切换)大于好处(避免CPU空转) →选轻量级锁/自旋
  • 如果T > S:CPU空转的消耗大于切换的节省 →选重量级锁/挂起

具体场景举例

场景锁持有时间推荐锁类型
给计数器i++加锁几纳秒轻量级锁(自旋)
写入一个大文件或网络IO几百毫秒重量级锁(挂起)
保护一段简单赋值极短无锁CAS更好

总结⭐

锁策略核心问题关键特性 / 适用场景
乐观锁 vs 悲观锁冲突概率多大?读多写少用乐观,写多用悲观
公平锁 vs 非公平锁锁该按什么顺序给?需要公平可配置,追求性能用非公平
可重入 vs 不可重入我能重复加这个锁吗?Java的锁基本都是可重入的
自旋锁 vs 挂起等待等锁时CPU让不让?临界区短用自旋,临界区长用挂起
读写锁读和写能拆开管吗?读多写少场景的终极优化利器
轻量级 vs 重量级加锁代价多大?竞争少用轻量,竞争多升级重量

锁升级

synchronized 锁自动升级路径⭐

为了解决重量级锁(挂起等待)带来的内核态切换开销,JDK 6引入了偏向锁轻量级锁,synchronized的锁状态会随着竞争情况逐步升级,且不可降级

锁升级路径为:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
synchronized 锁自动升级路径_可视化

简单说明

无锁 └─ 一个线程来了 → 偏向锁(记录线程 ID,不加锁就来) └─ 另一个线程也来了 → 轻量级锁(CAS 自旋,原地等待) └─ 自旋太久抢不到 → 重量级锁(系统互斥量,线程挂起排队)

对象头与锁状态

JVM通过对象头中的Mark Word来记录锁状态。不同状态下Mark Word的存储内容不同:

锁状态标志位偏向位存储内容说明
无锁010对象哈希码、分代年龄
偏向锁011持有锁的线程ID、偏向时间戳
轻量级锁00-指向栈中锁记录(Lock Record)的指针
重量级锁10-指向操作系统互斥量(Monitor)的指针

无锁 → 偏向锁(Biased Locking)

  • 思想:大多数时候,锁总是由同一个线程多次获取。JVM会偏向于第一个获取锁的线程。
  • 过程:当线程T1首次访问同步块时,JVM通过CAS将T1的线程ID写入对象头。之后T1再次进入同步块时,无需任何同步操作,直接执行。
  • 撤销:当线程T2尝试竞争锁时,JVM会暂停T1,检查T1是否仍在执行同步块。若已退出则撤销偏向锁;若仍在执行则升级为轻量级锁。
  • 注意:从JDK 15开始,偏向锁特性被标记为废弃,因为它在高并发场景下的维护成本(如撤销时的STW)甚至高于收益。

偏向锁 → 轻量级锁

  • 思想:多个线程虽然是竞争关系,但往往是交替执行,即“几乎没有实际竞争”。
  • 过程:线程在进入同步块前,在栈帧中创建锁记录(Lock Record),将Mark Word复制到锁记录中,然后通过CAS自旋尝试将对象头中的Mark Word替换为指向锁记录的指针。
  • 竞争失败:如果自旋等待后仍未获得锁,说明竞争加剧,锁膨胀为重量级锁

轻量级锁→ 重量级锁

  • 机制:依赖操作系统底层的互斥量(Mutex)实现。未获取到锁的线程不再自旋,而是进入阻塞态,等待被唤醒。
  • 代价:涉及系统调用和线程上下文切换,CPU开销大,但在高竞争场景下能保证系统吞吐量。

锁消除

锁消除(Lock Elimination)是一项编译器优化技术。JIT编译器在动态编译同步块时,如果通过逃逸分析(Escape Analysis)发现锁对象只被一个线程访问(即没有逃逸出当前线程),就会认为该锁不存在竞争,从而直接移除掉锁的申请与释放逻辑

典型场景:
在方法内部使用StringBuffer(线程安全,方法加锁)或Vector时,如果该对象是局部变量且未被其他线程引用,JIT就会大方地去掉锁。

// 优化前:看似每次append都要加锁publicStringbuildString(){StringBuffersb=newStringBuffer();// 局部变量,无逃逸sb.append("Hello");sb.append(" World");returnsb.toString();}// 优化后:JVM实际执行的效果相当于使用了无锁的StringBuilder

这项优化让我们不必过度担心使用线程安全类带来的性能损耗,只要作用域未逃逸,JVM会智能处理。

锁粗化

与锁消除相反,锁粗化(Lock Coarsening)解决的是锁操作过于零碎的问题。如果JIT检测到在一段代码中,相邻的多个同步块反复使用同一个锁对象,它会将这些零散的锁合并成一个范围更大的同步块。

典型场景:循环体内的加锁

// 优化前:每次循环都加锁、解锁for(inti=0;i<1000;i++){synchronized(this){doSomething();// 简单操作}}// 优化后:JVM将锁扩展到循环外部synchronized(this){for(inti=0;i<1000;i++){doSomething();}}

这样做虽然增大了单个线程的锁持有时间,但显著减少了加锁和解锁的次数,从而节省了CPU开销。

http://www.cnnetsun.cn/news/2140446.html

相关文章:

  • 手把手教学:雯雯的后宫-造相Z-Image-瑜伽女孩镜像部署常见问题解决
  • 一套真正有效的亚马逊SOP,应该解决哪些团队协作问题?
  • 千问3.5-9B赋能SpringBoot后端开发:智能API文档生成与逻辑校验
  • 网络安全渗透测试入门|无线安全渗透与防御完整教程
  • 美编饭碗不保?ChatGPT Images 2.0 的 12 个生产级玩法与提示词模板【附领取方式】
  • 05华夏之光永存・开源:黄大年茶思屋榜文解法「23期 5题」 【分布式收发机设计专项完整解法】
  • 使用 JavaScript 构建 Real-Anime-Z 前端交互界面:实时预览与参数调整
  • 关于C/C++轻量级HTTP协议解析项目需要注意的几个关键实现
  • Pixel Aurora Engine 对比YOLOv5:AI在生成与识别领域的协同应用
  • 告别编译失败!保姆级教程:用CMake+VS2019/2022搞定Poco库(含32/64位配置)
  • Sliding Window(滑动窗口)
  • Z-Image-ComfyUI应用实战:电商海报、社交配图生成,提升创作效率
  • 算法总结:二维网格 (Grid) DFS 遍历通用模板与实战解析
  • 企业想用AI做数据分析,但数据不能出内网,怎么办
  • M2FP从部署到应用:完整流程解析,快速实现多人图像语义分割
  • 品牌升级后卖不动,先别怪设计公司
  • 虚拟线程CPU爆表却吞吐不升?深度解析Java 25 Project Loom调度器v2.3内核变更,定位3类隐蔽资源饥饿场景
  • 分享一套锋哥原创的微信小程序校园宿舍管理系统(SpringBoot4后端+Vue3管理端)
  • YOLO11涨点优化:卷积魔改 | 引入Dirichlet Convolution (狄利克雷卷积),强化边界特征提取,提升重叠目标识别率
  • 别再为水下AI发愁了!手把手教你用虎鲸开源的UATD声呐数据集(含10类目标、9200张图)
  • Java 25密封类在微服务网关中的真实压测表现:TPS提升23%,错误分类精度达99.8%,附GraalVM原生镜像适配清单
  • 回合策略手游【船长请开炮代金券内购版】服务端搭建教程(含资源下载+部署过程)
  • DeepSeek V4大模型的技术解析与产业实践
  • Unity游戏视觉去马赛克技术解析:6款BepInEx插件实现原理与实战指南
  • CSS三大选择器终极对决!谁才是新手写样式的“最优解”?
  • SQL嵌套查询中常见报错排查_语法与权限处理
  • 别再死记硬背Word2Vec了!用Python+Gensim搞懂CBOW和Skip-gram的区别
  • 企业宣传视频制作:Sonic数字人实战案例,低成本生成专业内容
  • 国风美学生成模型v1.0快速体验:基于CSDN社区案例的模仿生成教程
  • Radxa ROCK E20C迷你网络设备:高性能路由器与轻量级NAS解析