📫作者简介:小明java问道之路 ,2022年度博客之星全国TOP3,专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化,文章内容兼具广度、深度、大厂技术方案,对待技术喜欢推理加验证,就职于知名金融公司后端高级工程师。
📫 热衷分享,喜欢原创~ 关注我会给你带来一些不一样的认知和成长。
🏆 2022博客之星TOP3 | CSDN博客专家 | 后端领域优质创作者 | CSDN内容合伙人
🏆 InfoQ(极客邦)签约作者、阿里云专家 | 签约博主、51CTO专家 | TOP红人、华为云享专家
🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~
🍅 文末获取联系 🍅 👇🏻 精彩专栏推荐订阅收藏 👇🏻
|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
| 🔥Redis从入门到精通与实战🔥 | Redis从入门到精通与实战 | 围绕原理源码讲解Redis面试知识点与实战 |
| 🔥MySQL从入门到精通🔥 | MySQL从入门到精通 | 全面讲解MySQL知识与企业级MySQL实战 |
| 🔥计算机底层原理🔥 | 深入理解计算机系统CSAPP | 以深入理解计算机系统为基石,构件计算机体系和计算机思维 |
| 🔥计算机底层原理🔥 | Linux内核源码解析 | 围绕Linux内核讲解计算机底层原理与并发 |
| 🔥数据结构与企业题库精讲🔥 | 数据结构与企业题库精讲 | 结合工作经验深入浅出,适合各层次,笔试面试算法题精讲 |
| 🔥互联网架构分析与实战🔥 | 企业系统架构分析实践与落地 | 行业最前沿视角,专注于技术架构升级路线、架构实践 |
| 🔥互联网架构分析与实战🔥 | 互联网企业防资损实践 | 互联网金融公司的防资损方法论、代码与实践 |
| 🔥Java全栈白宝书🔥 | 精通Java8与函数式编程 | 本专栏以实战为基础,逐步深入Java8以及未来的编程模式 |
| | 深入理解JVM | 详细介绍内存区域、字节码、方法底层,类加载和GC等知识 |
| | 深入理解高并发编程 | 深入Liunx内核、汇编、C++全方位理解并发编程 |
| | Spring源码分析 | Spring核心七IOC/AOP等源码分析 |
| | MyBatis源码分析 | MyBatis核心源码分析 |
| | Java核心技术 | 只讲Java核心技术 |
本文目录
[一、synchronized 的底层原理](#一、synchronized 的底层原理)
本文导读
Synchronized用的锁是存在java的对象头里面的。一个对象被new出来之后包含四个部分:对象头、类型指针、实例数据、对齐填充。
对象头 Mark Word 存储 了对象的 hashCode、GC信息、锁 信息三部分,这部分占8字节。每个对象都有个 monitor 对象,加锁就是在竞争 monitor 对象,代码块加锁是在前后分别加上 monitorenter 和 monitorexit 指令来实现的,方法加锁是通过一个标记位来判断的。
一、synchronized 的底层原理
synchronized是一种对象锁,是可重入 的,但是不可中断的(这个不可中断指的是在阻塞队列中排队是不可中断),非公平的锁。
加了synchronized后,在字节码会有二个指令monitorenter、monitorexit。
每个对象都是一个监视器锁(monitor),当monitor被占用时就会处于锁定状态,线程执行 monitorenter指令时尝试获取monitor的所有权,如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor 的所有者;如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝 试获取monitor的所有权。
执行monitorexit的线程必须是objectref所对应的monitor的所有者,指令执行时,monitor的进入数减 1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去 获取这个 monitor 的所有权。
**Monitor 对象存在于每个 Java对象的对象头里(存储的指针的指向)。**synchronized重量级锁是依赖对象内部的Monitor锁来实现的,而Monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也称为互斥锁。
二、对象头在JVM中存储的形式
Java对象头一般占有2个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit, 在64位虚拟机中,1个机器码是8个字节,也就是64bit)
对象头 主要由如下两个部分组成:Mark Word :存储的是 对象的 hashCode、锁信息、或分代年龄、GC标注等信息。Class Metadata Address: 存储对象所属类(元数据) 的指针,JVM通过这个确定这个对象属于哪个类。
实例数据:存放类的属性数据信息,包括父类的属性信息
对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
三、synchronized锁的优化(锁升级)
Monitor实现的锁是互斥锁也称为重量级锁,重量级锁 有一个缺点是线程开销很大。原因是当系统检测到锁是重量级锁后,会把等待的想要获得锁的线程进行阻塞,被阻塞的线程不会消耗CPU,但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态切换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还长。
synchronized一共有**四种锁状态,**锁的级别由低到高分别是:无锁状态->偏向锁状态->轻量级锁状态->重量级锁状态。这几个状态会随着锁的竞争情况逐渐升级(只能升级,不能降级)。
1、偏向锁、轻量级锁、重量级锁的升级过程
偏向锁偏向锁不是一把锁,而是代表了当前synchronized 锁状态。 当只有一个来线程加锁的时候,此时synchronized 锁就会变成偏向锁,偏向锁代表这个锁偏向这个线程,就是说当这个线程再次来加锁的时候,不需要再向操作系统申请资源,而是很快就能获取到锁,减少了申请锁的开销。
轻量级锁是当有多个线程参与到偏向锁的竞争中时,会先判断 markword 中的线程ID与这个线程是否一致,如果不一致,则会立即撤销偏向锁,升级为轻量级锁。
轻量级锁在代码进入同步块前,如果该同步块没有被锁定(即锁的标志位为"01"),那么JVM在将当前线程的栈帧中,创建一个 LockRecord(锁记录LR),并将锁对象头中的 markWord 信息复制到锁记录(LR)中,这个官方称为 Displaced Mard Word,然后线程尝试使用 CAS 将对象头中的 MarkWord 替换为指向锁记录(LR)的指针。
如果这个更新动作成功 了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后2bit)将转变为"00",即表示此对象处于轻量级锁状态。
如果失败,表示有其他线程竞争锁,当前线程便尝试使用自旋来获取锁。如果在自旋一定次数后仍为获得锁,那么轻量级锁将会升级成重量级锁。
2、偏向锁、轻量级锁、重量级锁的应用场景
总结
synchronized用的锁是存在java的对象头里面的,加了synchronized后,在字节码会有二个指令monitorenter、monitorexit。
Monitor 对象存在于每个 Java对象的对象头里(存储的指针的指向)。
synchronized重量级锁是依赖对象内部的Monitor锁来实现的,而Monitor又依赖操作系统的MutexLock(互斥锁)来实现的。
synchronized一共有四种锁状态,锁的级别由低到高分别是:无锁状态->偏向锁状态->轻量级锁状态->重量级锁状态。