news 2026/5/1 20:20:23

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多线程——面试中常考的内容(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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 20:12:24

MARS算法原理与Python实现:非线性回归实战指南

1. MARS算法核心原理拆解多元自适应回归样条(Multivariate Adaptive Regression Splines)是一种非线性回归技术,由Jerome Friedman在1991年提出。它通过分段线性回归的方式自动构建预测模型,特别适合处理高维数据中的复杂非线性关系。1.1 基础数学框架MA…

作者头像 李华
网站建设 2026/5/1 20:12:23

解锁旧Mac新生命:OpenCore Legacy Patcher完全指南

解锁旧Mac新生命:OpenCore Legacy Patcher完全指南 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 你是否还在为心爱的旧Mac无法升级最新macOS而烦…

作者头像 李华
网站建设 2026/5/1 20:10:22

3步释放被锁音乐:qmc-decoder高效解密QQ音乐文件实战指南

3步释放被锁音乐:qmc-decoder高效解密QQ音乐文件实战指南 【免费下载链接】qmc-decoder Fastest & best convert qmc 2 mp3 | flac tools 项目地址: https://gitcode.com/gh_mirrors/qm/qmc-decoder 你是否遇到过这样的情况:在QQ音乐下载了喜…

作者头像 李华