首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【javaEE】多线程进阶--锁策略

【javaEE】多线程进阶--锁策略

作者头像
那我掉的头发算什么
发布2026-01-12 19:44:35
发布2026-01-12 19:44:35
1040
举报
这里是@那我掉的头发算什么 刷到我,你的博客算是养成了😁😁😁

常见的锁策略

我们学习的synchronized是一个非常全面的锁,但是,如果想要进一步扩展锁的功能与使用,需要关注锁策略。 这些锁策略不是局限于java,任何的语言涉及到锁相关的内容都可以谈到这样的锁策略。

悲观锁VS乐观锁

不是针对某一种锁,而是某一种锁有悲观or乐观的属性 描述的是加锁时遇到的场景 悲观锁:加锁的时候,预测到接下来锁的竞争会很激烈,需要针对这样的激烈情况额外做一些工作 乐观锁:加锁的时候,预测到接下来锁的竞争不会很激烈,不需要额外做一些工作

轻量级锁VS重量级锁

遇到场景之后的解决方案 轻量级锁:应对乐观的场景,付出的代价小=》更高效 重量级锁:应对悲观的场景,付出更多的代价=》更低效

挂起等待锁VS自旋锁

挂起等待锁:重量级锁的典型实现 --操作系统内核级别,加锁的时候发现竞争,就会使线程进入阻塞态,后续需要内核进行唤醒 自旋锁:应用程序级别的锁,加锁的时候发现竞争,一般不会阻塞,而是通过忙等的形式来等待。 忙等在上一章中定时器部分提到过,忙等只适用于乐观锁,本身遇到竞争的概率就小,忙等在短时间内就能拿到锁,结束忙等,所以可以接受忙等。

在这里插入图片描述
在这里插入图片描述

自旋锁优缺点: 优点: 没有放弃 CPU, 不涉及线程阻塞和调度, ⼀旦锁被释放, 就能第⼀时间获取到锁. 缺点: 如果锁被其他线程持有的时间⽐较久, 那么就会持续的消耗 CPU 资源. (⽽挂起等待的时候是不 消耗 CPU 的).

以上三种锁之间的联系: 乐观锁=》轻量级锁=》自旋锁 悲观锁=》重量级锁=》挂起等待锁

synchronized是什么锁? synchronized是一个自适应性的锁,锁冲突小时它就变成轻量级锁、自旋锁,锁冲突大时它就变成重量级锁,挂起等待锁。

普通读写锁VS互斥锁

读写锁:写锁和写锁、写锁和读锁之间互斥,读锁与读锁之间不互斥。 互斥锁:都互斥 多个线程读写同一个数据,这个本身就是线程安全。 但是多个线程读写数据,两三个线程修改数据会涉及线程安全问题。

读写锁正是适用于这种读多写少的场景。如果读锁和读锁之间也互斥,那么锁冲突很严重,并且没必要,影响效率。保证线程安全的前提下,提高效率。

可重入锁VS不可重入锁

可重入锁:一个线程,一把锁,加锁多次,没有变成死锁 核心要点: 锁要记录当前是哪个线程在持有这把锁 使用计数器,记录加锁的次数,在合适位置解锁

公平锁VS非公平锁

当一个锁被释放时,这个锁给谁? 两种情况: 给最先开始等待这把锁的线程 概率均等,随机给

其实这两种概念都可以说是“公平”,但是定义这个的人认为第一种是公平,那么自然第二种也就是非公平。 synchronized采用随机调度方法,是非公平锁。

详谈synchronized原理

基本特点

结合上⾯的锁策略, 我们就可以总结出, synchronized 具有以下特性(只考虑 JDK 1.8):

  1. 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁.
  2. 开始是轻量级锁实现, 如果锁被持有的时间较⻓, 就转换成重量级锁.
  3. 实现轻量级锁的时候⼤概率⽤到的⾃旋锁策略
  4. 是⼀种不公平锁
  5. 是⼀种可重⼊锁
  6. 不是读写锁

锁升级

jvm将锁分为下面四种状态,根据情况进行升级:

在这里插入图片描述
在这里插入图片描述
偏向锁

第⼀个尝试加锁的线程, 优先进⼊偏向锁状态. 偏向锁不是真的 “加锁”, 只是给对象头中做⼀个 “偏向锁的标记”, 记录这个锁属于哪个线程.如果后续没有其他线程来竞争该锁, 那么就不⽤进⾏其他同步操作了(避免了加锁解锁的开销)如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别当前申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进⼊⼀般的轻量级锁状态.偏向锁本质上相当于 “延迟加锁” . 能不加锁就不加锁, 尽量来避免不必要的加锁开销.但是该做的标记还是得做的, 否则⽆法区分何时需要真正加锁。

偏向锁本质上也是懒汉模式的思想的体现。

轻量级锁/自旋锁

随着其他线程进⼊竞争, 偏向锁状态被消除, 进⼊轻量级锁状态(⾃适应的⾃旋锁). 此处的轻量级锁就是通过 CAS 来实现. • 通过 CAS 检查并更新⼀块内存 (⽐如 null => 该线程引⽤) • 如果更新成功, 则认为加锁成功 • 如果更新失败, 则认为锁被占⽤, 继续⾃旋式的等待(并不放弃 CPU). ⾃旋操作是⼀直让 CPU 空转, ⽐较浪费 CPU 资源.因此此处的⾃旋不会⼀直持续进⾏, ⽽是达到⼀定的时间/重试次数, 就不再⾃旋了.也就是所谓的 "⾃适应“,此时就会将锁变成重量级锁。

重量级锁

执⾏加锁操作, 先进⼊内核态. • 在内核态判定当前锁是否已经被占⽤ • 如果该锁没有占⽤, 则加锁成功, 并切换回⽤⼾态. • 如果该锁被占⽤, 则加锁失败. 此时线程进⼊锁的等待队列, 挂起. 等待被操作系统唤醒. • 经历了⼀系列的沧海桑⽥, 这个锁被其他线程释放了, 操作系统也想起了这个挂起的线程, 于是唤醒这个线程, 尝试重新获取锁.

锁消除

编译器优化的体现。编译器会判定当前代码是否需要加锁,如果加的锁是无意义的锁,编译器会自动把synchronized消除,以此来提高效率。 此处的优化不像之前提到的优化那样造成线程安全问题。这里的优化是很保守的,在保证线程安全的情况下进行优化。所以这意味着,编译器只会优化掉它百分百确定的多加的锁,所以可能会漏掉该优化的锁。所以不能单单依靠编译器的优化来避免多使用锁,而是使用时就需要注意。

锁粗化

锁的粗化是用来修饰锁的粒度的。 加锁和解锁之间包含的代码越多,就认为锁粒度越粗,包含的代码越少,就认为锁粒度越细。(代码的多少不是指行数,而是指实际执行的次数/时间,比如循环代码就是很多)

在这里插入图片描述
在这里插入图片描述

一段代码中,反复针对细粒度的代码加锁,就可能被替换成更粗粒度的加锁,如上。如果是细粒度的写法,先不说加锁解锁的消耗,每次加锁还可能涉及到竞争。 代价: 粗粒度的代码会导致程序并发程度不高,代码串行化,所以不是任何时候锁粗化都是好的,而是有条件的。

在这里插入图片描述
在这里插入图片描述

总结

本文围绕锁策略与synchronized原理展开,锁策略以 “场景权衡” 为核心,涵盖悲观 / 乐观、轻量 / 重量级等六大分类,且存在 “乐观锁→轻量锁→自旋锁”“悲观锁→重量级锁→挂起等待锁” 的对应关联;而 JDK1.8 的synchronized是自适应锁,兼具可重入、非公平等特性,通过 “无锁→偏向锁→轻量级锁→重量级锁” 的动态升级适配不同竞争场景,搭配锁消除、锁粗化优化,最终在线程安全与执行效率间实现最优平衡。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 这里是@那我掉的头发算什么 刷到我,你的博客算是养成了😁😁😁
  • 常见的锁策略
    • 悲观锁VS乐观锁
    • 轻量级锁VS重量级锁
    • 挂起等待锁VS自旋锁
    • 普通读写锁VS互斥锁
    • 可重入锁VS不可重入锁
    • 公平锁VS非公平锁
  • 详谈synchronized原理
    • 基本特点
    • 锁升级
      • 偏向锁
      • 轻量级锁/自旋锁
      • 重量级锁
    • 锁消除
    • 锁粗化
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档