---
title: 《面试实录》百度二面，Sychronized 原理详解
category: 
  - 对线面试官
  - 面试实录
  - Java
tag:
  - Java
  - Map
head:
  - - meta
    - name: keywords
      content: 面试实录,Sychronized,JAVA,大厂面试,锁
    - name: description
      content: 百度二面，Sychronized原理详解。涉及知识点：synchronized加锁原理、锁特性、锁升级过程
---
本期是【**面试实录**】系列文章的第**1**期，持续更新中.....。

- 欢迎关注+订阅，持续更新中！！！致力打造校招核心面试攻略~
- 根据秋招春招上岸大厂面试经历以及身边朋友**上岸面试录音模拟面试现场**，并整合面试常考知识点，通俗有趣的去讲解 `八股文`，不一样的系列，轻松掌握知识~

【**面试实录**】专栏系列目前已经连载 **1** 篇了，据说看了这个系列的朋友都拿到了大厂offer~

# 序言

大家好，我是小龙。

曾几何时，大家是否有这样的困惑？

知道相关的知识点，但是面试时面试官**换一种问法**，你便不知道怎样回答，**给你一个场景**，你就联系不到相关的知识点上，或者不知道用对应知识去分析相关问题。

比如 "`你知道垂直定理，但是你遇到相关几何题，就是想不到用这个定理去解决问题"`。

新的篇章将开启，本系列会`结合小龙面试经历`以及`身边上岸大厂的朋友们的面试经历、面试录音、面试分享`，尽可能**还原面试现场**。

带大家**感受面试现场**，**结合情景学会灵活的应用知识**。看一看大佬们都是怎样面试的。

记得每期结束，问问自己，你真的对知识掌握了吗？

# 考题速查

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

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

# 面试现场

叮叮叮......

**面试官**：“你好，我是XX面试官，请问是小龙吗？”

**小龙**：“您好，面试官，我是小龙”

**面试官**：“好的，现在有空吗，我们开始面试吧”

**小龙**：“嗯嗯，准备好啦”

.......

other questions

.......

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

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

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

**小龙**：“这个当然不安全，**因为访问静态** **`synchronized`** **方法占用的锁是当前类的锁，而访问非静态** **`synchronized`** **方法占用的锁是当前实例对象锁**。”

**面试官**：“好的，那你知道 `synchronized` 实现加锁的本质原理吗？”

**独白**：“这个不就是 monitorenter 那两条命令吗，拿捏~”

**小龙**：“如果 synchronized 修饰代码块，javac 编译时，会在代码块前后生成 `monitorenter` 和 `monitorexit` ，当执行遇到 monitorenter指令便会去尝试获取锁。”

**独白**：“以为拿捏了，为了让面试官觉得我理解的很透彻，再补充了一点。”

**小龙**：“使用 synchronized 在出现异常时，还可以保证锁的释放。因为它还会隐式的加一个 try-finnaly，finnaly 中也有 monitorexit 命令以便出现异常可以释放锁。”

**面试官**：“好的，你说的这些没问题。那你知道当执行到 monitorenter 指令时，它是怎样去尝试获取锁的吗？这个锁究竟是啥？你还是没说明白呢，哈哈。”

**独白**：“原来想问 monitor 噢，还是很基础的。”

**小龙**：“其实追根朔源，每个对象都有一个 `monitor` 与之关联，而当且一个 monitor 被持有后，它便处于锁定状态啦，而线程执行到 monitorenter 指令时，便会尝试获取对象所对应的 monitor 的所有权，即尝试获得对象的锁。”

> Moniter=WaitSet+EntryList（当锁被占用，其他线程来就会进入阻塞队列，等锁释放再一起竞争）+Owner（指向持有锁的线程）

![img](https://xiaolongcoder.oss-cn-beijing.aliyuncs.com/imgs/Java2Top/java/k202304051803291.png)

**面试官**：“真的是这样吗？还有吗？”

**小龙**："你别急嘛，我还没说完，上面说法其实不完全正确，那是针对重量级锁。"

**小龙**：“如果是 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）组成上面说过了，此处不再赘述

![img](https://xiaolongcoder.oss-cn-beijing.aliyuncs.com/imgs/Java2Top/java/k202304051803227.png)

**小龙**：“然后让锁记录中 `Object reference` 指向锁对象，并尝试用 CAS（原子操作）替换 Object 的 Mark Word，将 Mark Word 的值存入锁记录；”

![img](https://xiaolongcoder.oss-cn-beijing.aliyuncs.com/imgs/Java2Top/java/k202304051803174.png)

**小龙**：“如果 CAS 替换成功，对象头中存储了`锁记录地址和状态00`，表示由该线程给对象加锁；”

![img](https://xiaolongcoder.oss-cn-beijing.aliyuncs.com/imgs/Java2Top/java/k202304051803683.png)

**面试官**：“那如果失败又是怎样处理的呢？”

**小龙**：“如果cas失败，有两种情况：”

- 如果是其它线程已经持有了该 Object 的轻量级锁，这时表明有竞争，进入锁膨胀过程（也就是升级成重量级锁---monitor）
- 如果是自己执行了 synchronized 锁重入，那么再添加一条 Lock Record 作为重入的计数。

> 同一线程对同一对象加了多次锁--锁重入

![img](https://xiaolongcoder.oss-cn-beijing.aliyuncs.com/imgs/Java2Top/java/k202304051803195.png)

**小龙**：“当退出 synchronized 代码块（解锁时）如果有取值为 null 的锁记录，表示有重入，这时重置锁记录表示重入计数减一；”

**小龙**：“当退出 synchronized 代码块（解锁时）锁记录的值不为 null，这时使用 CAS 将 Mark Word 的值恢复给对象头（也就是将之前锁记录和对象CAS替换的部分又替换回来，换回原来各自的）；”

**小龙**：“若上面操作成功，则解锁成功；失败，说明轻量级锁进行了锁膨胀或已经升级为重量级锁，进入重量级锁解锁流程。”

`重量级锁`

**面试官**：“那重量级锁又是怎么回事呢？”

**小龙**：“如果在尝试加轻量级锁的过程中，CAS 操作无法成功，这时一种情况就是有其它线程为此对象加上了轻量级锁（有竞争)，这时需要进行锁膨胀，将轻量级锁变为重量级锁。”

**小龙**：“当 T1 进行轻量级加锁时，T0 已经对该对象加了轻量级锁。”

**小龙**：“这时 T1 加轻量级锁失败，进入锁膨胀流程 （因为 T1 加锁失败，被 T0 占了，但是 T1 不能在这干耗着啊，于是进入锁膨胀，申请一个monitor 去阻塞，升级成重量级锁--这时才有阻塞）”

**小龙**：“实际上就是为 Object 对象申请 Monitor 锁，让 Object 指向重量级锁地址。然后自己进入 Monitor 的 EntryList BLOCKED ”

![img](https://xiaolongcoder.oss-cn-beijing.aliyuncs.com/imgs/Java2Top/java/k202304051803885.png)

**小龙**：“当 T0 退出同步块解锁时，使用 `CAS` 将 Mark Word 的值恢复给对象头，失败（`因为object mark-word 里放的是 monitor对象的地址了，不是T0 的 Lock Recode里面那个地址了`）。这时会进入重量级解锁流程，即按照 Monitor 地址找到 Monitor 对象，设置 Owner为 null，唤醒 EntryList 中 BLOCKED 线程。”

**面试官**：“给个大大的赞，继续加油！”

# 知识总结

本期我们通过面试模拟深入探讨了 `synchronized` 的底层实现原理，下期再见。**订阅+关注** 持续追更。

## **面试重点**

`synchronized加锁原理`、`锁特性`、 `锁升级过程`

