你好,面试官 | 百度是这样问 synchronized 的.....
本期是【你好,面试官】系列文章的第9期,持续更新中.....。
回复"面试笔记"【点击】获取小龙秋招面试笔记,已助力 N 名同学斩获offer~,速来。
《你好,面试官》系列目前已经连载9篇了,据说看了这个系列的朋友都拿到了大厂offer~
你好,面试官 | 你真的理解面向 "对象"? 你好,面试官 | 我用Java List 狂怼面试官~ 你好,面试官 | 你拿Java Map考验老干部? 你好,面试官 | 你觉得MySQL自增主键连续吗? 你好,面试官 | 呵,我怎么可能不知道索引! 你好,面试官 | 我不仅知道MySQL索引,还会优化呢~ 你好,面试官 | 我可以凭借MySQL架构体系,事务锁机制原理进你公司吗? 你好,面试官 | 初识 Java 并发,拿捏这些业务可能遇到的问题~ 欢迎星标+订阅,持续更新中。。。致力打造校招核心面试攻略~
小龙有话说
本期会通过面试模拟复现百度提前批二面
,关于 synchronized
的考察,以此讲解其原理。
本期题改编自 ——2022届秋招 百度 二面
面试现场
叮叮叮......
面试官:“你好,我是XX面试官,请问是小龙吗?”
小龙:“您好,面试官,我是小龙”
面试官:“好的,现在有空吗,我们开始面试吧”
小龙:“嗯嗯,准备好啦”
.......
other questions
.......
面试官:“synchronized
分别修饰了一个静态方法和一个实例方法,现在对其并发访问,这是否线程安全?”
独白:“我记不太清原问题咋问的了,反正大概考的知识点就是这个,不是直白的考,而是给你一个实际例子叫你分析”
独白:“当时可能刚开始面试第一个问题还没进入状态,又有点紧张,面试官说话又不清楚,所以一下还没反应过来,卡了几秒,映像比较深刻。”
小龙:“这个当然不安全,因为访问静态 synchronized
方法锁的是当前类的Class对象,而访问非静态 synchronized
方法锁的是当前实例对象。”
面试官:“好的,那你知道 synchronized
实现加锁的本质原理吗?”
独白:“这个不就是 monitorenter 那两条命令吗,拿捏~”
小龙:“如果 synchronized 修饰代码块,javac 编译时,会在代码块前后生成 monitorenter
和 monitorexit
,当执行遇到 monitorenter指令便会去尝试获取锁。”
独白:“以为拿捏了,为了让面试官觉得我理解的很透彻,再补充了一点。”
小龙:“使用 synchronized 在出现异常时,还可以保证锁的释放。因为它还会隐式的加一个 try-finnaly,finnaly 中也有 monitorexit 命令以便出现异常可以释放锁。”
面试官:“好的,你说的这些没问题,那你知道当执行到 monitorenter 指令时,它是怎样去尝试获取锁的吗?这个锁究竟是啥?你还是没说明白呢,哈哈。”
独白:“原来想问 monitor 噢,还是很基础的,幸好这些在【面试笔记
】都详细总结过。”
小龙:“其实追根朔源,每个对象都有一个 monitor
与之关联,而当且一个 monitor 被持有后,它便处于锁定状态啦,而线程执行到 monitorenter 指令时,便会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。”
Moniter=WaitSet+EntryList(当锁被占用,其他线程来就会进入阻塞队列,等锁释放再一起竞争)+Owner(指向持有锁的线程)
面试官:“真的是这样吗?还有吗?”
小龙:"你别急嘛,我还没说完,上面说法其实不完全正确,那是针对重量级锁。"
小龙:“如果是 synchronized 没被优化之前,它是重量级锁,仅依赖对象对应的 moniter,但是后面进行了优化。”
面试官:“噢,展开说说。”
小龙:“我们的对象总的来说是由 对象头、实例数据、对齐填充构成,而对象头里面就存了 Mark Word
,Mark Word 里面存了对象自身的一些运行信息,比如:hashcode、GC分代年龄、锁状态标志、持有的锁
。”
小龙:“若 synchronized 给该对象加锁后,那么该对象头的 Mark Word 就会发生相应的变化,优化后的 synchronized 会迎合不同场景升级锁,随着锁升级,这个变化也不同。”
小龙:“偏向锁依赖 当前线程ID
,重量级锁依赖 monitor
,轻量级锁依赖 锁记录lock-record
。”
独白:“应该是说清楚啦,更多装逼小技巧看【面试笔记
】”
面试官:“好的,你说详细说一下锁的升级过程吗?”
小龙:“synchronized 总的升级流程是这样:无锁 ----> 偏向锁 ----> 轻量级锁----> 锁自旋 ----> 重量级锁。”
偏向锁
小龙:“首先会判断Mark Word里面是否有当前线程Id,若有则处于偏向锁,若无则尝试用 CAS 将 Mark Word 替换为线程Id,若成功则偏向锁设置成功,失败则有竞争要升级成轻量级锁。”
轻量级锁
小龙:“而对于轻量级锁里面涉及的就更复杂,详细展开说就是,开始会创建锁记录(Lock Record)对象,然后我们每个线程的栈帧都会包含一个锁记录
的结构,内部可以用来存储锁定对象的 Mark Word;”
锁记录(Lock Record)包含了 lock record 地址 00、Object reference
对象(Obejct)组成上面说过了,此处不再赘述
小龙:“然后让锁记录中 Object reference
指向锁对象,并尝试用 CAS(原子操作)替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录;”
小龙:“如果 CAS 替换成功,对象头中存储了锁记录地址和状态00
,表示由该线程给对象加锁;”
面试官:“那如果失败又是怎样处理的呢?”
小龙:“如果cas失败,有两种情况:”
如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程(也就是升级成重量级锁---monitor) 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数。
同一线程对同一对象加了多次锁--锁重入
小龙:“当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录表示重入计数减一;”
小龙:“当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 CAS 将 Mark Word 的值恢复给对象头(也就是将之前锁记录和对象CAS替换的部分又替换回来,换回原来各自的);”
小龙:“若上面操作成功,则解锁成功;失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程。”
重量级锁
面试官:“那重量级锁又是怎么回事呢?”
小龙:“如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。”
小龙:“当 T1 进行轻量级加锁时,T0 已经对该对象加了轻量级锁。”
小龙:“这时 T1 加轻量级锁失败,进入锁膨胀流程 (因为 T1 加锁失败,被 T0 占了,但是 T1 不能在这干耗着啊,于是进入锁膨胀,申请一个monitor 去阻塞,升级成重量级锁--这时才有阻塞)”
小龙:“实际上就是为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址。然后自己进入 Monitor 的 EntryList BLOCKED ”
小龙:“当 T0 退出同步块解锁时,使用 CAS
将 Mark Word 的值恢复给对象头,失败(因为object mark-word 里放的是 monitor对象的地址了,不是T0 的 Lock Recode里面那个地址了
)。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner为 null,唤醒 EntryList 中 BLOCKED 线程。”
这只是为了让大家全面详细的了解整个锁升级过程,其实面试不用回答那么完整,只需要简单说一下整个流程便可以,如果面试官要深问,再细讲。不过【面试笔记】全都给大家把关键点提炼出来啦,按着回答问题不大
面试官:“给个大大的赞,继续加油!”
独白:“不愧是我,真男人是也!【面试笔记
】在手,大厂 offer 不愁。”
知识总结
本期我们通过面试模拟深入探讨了 synchronized
的底层实现原理,下期分析 volatile相关
。订阅+星标持续追更。
面试重点
synchronized
的底层实现原理.
点击下方“阅读原文”直达视频,或者B站搜索:小龙coding
求一键三连:希望转发、在看、分享给更多同学哟~
公众号:大厂进阶指南,专注分享后端技术、校招面试求职