你好,面试官 | 百度是这样问 synchronized 的.....

JavaEdge

共 3769字,需浏览 8分钟

 ·

2022-06-06 18:22

本期是【你好,面试官】系列文章的第9期,持续更新中.....。

回复"面试笔记"【点击获取小龙秋招面试笔记,已助力 N 名同学斩获offer~,速来。

你好,面试官》系列目前已经连载9篇了,据说看了这个系列的朋友都拿到了大厂offer~

小龙有话说

本期会通过面试模拟复现百度提前批二面,关于 synchronized 的考察,以此讲解其原理。

本期题改编自 ——2022届秋招 百度 二面

面试现场

叮叮叮......

面试官:“你好,我是XX面试官,请问是小龙吗?”

小龙:“您好,面试官,我是小龙”

面试官:“好的,现在有空吗,我们开始面试吧”

小龙:“嗯嗯,准备好啦”

.......

other questions

.......

面试官:“synchronized 分别修饰了一个静态方法和一个实例方法,现在对其并发访问,这是否线程安全?”

独白:“我记不太清原问题咋问的了,反正大概考的知识点就是这个,不是直白的考,而是给你一个实际例子叫你分析

独白:“当时可能刚开始面试第一个问题还没进入状态,又有点紧张,面试官说话又不清楚,所以一下还没反应过来,卡了几秒,映像比较深刻。

小龙:“这个当然不安全,因为访问静态 synchronized 方法锁的是当前类的Class对象,而访问非静态 synchronized 方法锁的是当前实例对象。”

面试官:“好的,那你知道 synchronized 实现加锁的本质原理吗?”

独白:“这个不就是 monitorenter 那两条命令吗,拿捏~

小龙:“如果 synchronized 修饰代码块,javac 编译时,会在代码块前后生成 monitorentermonitorexit ,当执行遇到 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



---END---

求一键三连希望转发在看分享给更多同学哟~

公众号:大厂进阶指南,专注分享后端技术、校招面试求职

相逢必是缘分,希望大家给个小小的关注您的支持是我莫大的动力,更多优质好文等您来探索,爱你哟
同时,我也是 B 站 up 主小龙coding,日常分享高质量资料,输出面试、工作经验、欢迎围
浏览 65
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报