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

多线程——面试中常考的内容(11)

从这次开始,多线程的学习进入了一个由工作、面试常用转变为仅面试要考的部分,更侧重一些八股文的性质,说白了就是背,虽然对我这种文科生来说无所谓......希望大家对这些知识能有所了解,因为这些考到的频率仍然很高。

常见的锁策略

如果你自己需要实现一把锁(你认为标准库给你提供的锁不够用),你需要关注锁策略。

其实synchronized已经非常好用了,足以覆盖绝大多数的使用场景。

1.悲观锁vs乐观锁

这不是针对某一种具体的锁,而是某个锁具有“悲观”或者“乐观”特性。

(1)悲观锁:加锁的时候,预测接下来的锁竞争情况非常激烈,就需要针对这样的情况额外做一些工作(比如有一把锁,有二十个线程尝试获取锁,每个线程加锁的频率都很高,一个线程加锁的时候,很可能被另一个线程占用着)。

(2)乐观锁:加锁的时候,预测接下来的锁竞争的情况不激烈,就不需要做额外工作(有一把锁,假设只有两个线程尝试获取这把锁,每个线程加锁的频率都很低,一个线程加锁的时候,大概率另一个线程没有和他竞争)。

2.重量级锁vs轻量级锁

重量级锁,在悲观的场景下,此时就要付出更多的代价(更低效)

轻量级锁,在乐观的场景下,此时付出的代价会更小(更高效)

3.挂起等待锁vs自旋锁

挂起等待锁是重量级锁的典型实现,操作系统内核级别的,加锁的时候发现竞争,就会使该线程进入阻塞状态,后续就需要内核进行唤醒了。

自旋锁是轻量级锁的典型实现,是应用程序级别的,加锁的时候发现竞争,一般也不是进入阻塞,而是通过忙等的方式来进行等待。

因此,对应关系为:悲观锁——重量级锁——挂起等待锁

乐观锁——轻量级锁——自旋锁

synchronized在前三种锁策略当中充当的角色,并不是其中的任何一方,而是自适应的,JVM内部会统计每个锁竞争的激烈程度,如果竞争不激烈,就对应乐观锁一方;反之则对应悲观锁一方。

4.普通互斥锁vs读写锁

普通互斥锁就是synchronized加锁与解锁;读写锁就是加锁中分为读方式加锁与写方式加锁,还有解锁,一共三种。

多个线程读取一个数据,本身就是数据安全的,但多个线程读取时,即使有一个线程修改,也会涉及到线程安全问题。

读写锁适用于读多写少的情况,大部分操作在读,少数操作在写,如果你把读和写加上普通的互斥锁,意味着锁冲突非常严重,而读写锁确保读锁与读锁之间不是互斥的(不会产生阻塞)。也就是说,读锁与写锁之间,才会产生互斥,写锁与写锁之间也会产生互斥。

这样,在保证线程安全的前提下,降低锁冲突的概率,提高效率。

当然,synchronized不是读写锁。

5.可重入锁vs不可重入锁

可重入这个概念,我们之前已经提过了,就是一个线程一把锁,连续加锁多次,如果不构成死锁,就是可重入的。

synchronized是可重入锁。

6.公平锁vs非公平锁

公平锁就是要自己设定一个公平的条件;非公平锁之下,锁默认情况下,操作系统针对线程的调度是随机的。

可是,要实现公平锁,需要付出额外的东西,比如,需要使用一个队列,记录一下各个线程获取锁的顺序等。

synchronized是非公平锁。

synchronized自适应的过程——锁升级

从上到下顺序如下:

当然只能升级,不能降级。

那么,偏向锁是什么?

偏向锁

它本质上是一个懒汉模式,进行synchronized,刚一上来不是真加锁,而是简单做一个标记,这个标记非常轻量,相比于加锁解锁来说,效率高很多。

如果没有线程来竞争这个锁,最终当前线程执行到解锁代码,也就只是简单清除上述的标记即可(不涉及真加锁,真解锁)。

如果有其他线程来竞争,就抢先一步在另一个线程拿到锁之前,抢先拿到锁,一旦加锁,偏向锁就会变成轻量级锁,其他线程只能阻塞等待。

因此,整个升级过程:

(1)无锁到偏向锁——代码进入synchronized板块

(2)偏向锁到轻量级锁——拿到偏向锁的线程运行过程中,遇到了其他线程竞争这个锁

(3)轻量级锁到重量级锁——JVM发现,当前竞争锁的情况非常激烈

锁消除

这也是编译器优化的一种体现。编译器会判定,当前这个代码逻辑是否真的需要加锁,如果确实不需要加锁,但是你写了synchronized,就会自动把synchronized去掉。

当它100%确定你这个代码是单线程的,才会真正触发消除,像一些判定不清楚的情况,是不会触发的,因此我们不应到处synchronized,不然优化机制只能把其中一部分能明确判定的优化掉,还会有很多不应该使用,但是编译器也优化不调。目前还是不能完全依赖编译器优化。

锁粗化

加锁和解锁之间,包含的代码越多(不是代码行数,而是实际执行的指令与时间),就认为锁的粒度就越粗,反之就越细。

一个代码中,反复针对细粒度的代码加锁,就可能被优化成更粗粒度的加锁。

如图,多次加锁与解锁,粗化为在开始与结束时加锁与解锁。

当然,也不是说,锁的粒度越粗,就越好,锁粗了,就会影响到线程的并发程度。

如果确实三件事,本身都需要加锁,粗化成一把锁是合理的;但如果三件事里两件事需要加锁,另一件事不需要,此时粗化,就把能并行执行的事情也变成串行了。

比如,下面这个就可以粗化:

CAS的比较与切换

多线程中,有个叫CAS的,日常开发中很少用,但很多地方有他的影子。

下图是它的执行过程:

当然,CAS最重要的用途就是实现原子类,使用原子类的目的,就是避免加锁。

原子类,专有名词,特指atomic这个包里的类。

之前谈到的通过synchronized保证一个修改的原子性,和“原子类”这样的术语是不相关的。

今天就到这里,明天我们继续。

我的gitee链接:https://gitee.com/QQ2240635095/java4_10.git

http://www.jsqmd.com/news/733731/

相关文章:

  • 3步彻底解决Visual C++运行库问题:VisualCppRedist AIO完全指南
  • Lean 4定理验证:方法论与工程实践
  • PHP V6 单商户常见问题——升级提示mkdir()处理方案
  • 终极二维码修复指南:QRazyBox让你的失效二维码重获新生
  • 2026 佐米曲普坦临床选药与评测深度指南:偏头痛患者的高性价比优选 - GrowthUME
  • MARS算法原理与Python实现:非线性回归实战指南
  • 【c++】异常处理
  • MCP 2026医疗数据安全防护“红蓝对抗”实战手册(内部流出版):覆盖CT/MRI/病理全模态攻击链与17个防御卡点
  • 01 用栈实现队列
  • 大气层系统完整指南:Switch自定义固件的终极解决方案
  • Moonlight-Switch:Nintendo Switch游戏串流技术方案与多平台兼容架构
  • taotoken 平台 python 调用 openai 兼容 api 的完整入门指南
  • 借助模型广场与官方折扣为新项目选择高性价比模型
  • 解锁旧Mac新生命:OpenCore Legacy Patcher完全指南
  • C++中string常用方法总结
  • 2026年扬州工厂短视频代运营案例分析 - 速递信息
  • 2026企业AI陪跑推荐:全程陪伴,落地见效 8 - 速递信息
  • 【Laravel AI Security Alert】:2026年Q1已爆发7起Prompt注入+模型越权调用事件,3步修复框架层RCE风险(附CVE-2026-XXXX PoC)
  • Laravel 12模型层AI增强成本封顶设计:3种可插拔式Token配额策略,让每个Eloquent操作自带预算守门员
  • 别再乱配CORS了!Flask-CORS从入门到生产环境安全配置实战(含Nginx反向代理)
  • 基于AI与现金流模拟的自托管个人财务预测机器人开发实践
  • CompressO:如何用这款免费开源工具将视频图片压缩90%以上
  • 为AI代码生成器Cursor配置ESLint与Prettier规则集,实现自动化代码规范检查与格式化
  • 2026连云港黄金回收市场深度解析与靠谱品牌推荐 - 速递信息
  • 【黑马点评日记】异步秒杀:异步线程和阻塞队列以及Lua脚本的相关流程分析
  • R语言偏见检测不可绕过的5个统计陷阱,第3个让OpenAI内部报告延迟发布117天
  • EpiCaR集成学习:动态修正认知不确定性的高效推理方法
  • 【Swoole × LLM 企业级落地白皮书】:3类高敏业务(智能工单、实时投顾、IoT边缘推理)的长连接架构选型决策树与SLA保障方案
  • 多模态模型小型化:挑战与优化策略
  • 2026真心问:重庆本地家教哪家靠谱? - 速递信息