前言

首先,synchronized 是什么?我们需要明确的给个定义——同步锁,没错,它就是把

可以用来干嘛?锁,当然当然是用于线程间的同步,以及保护临界区内的资源。我们知道,锁是个非常笼统的概念,像生活中有指纹锁、密码锁等等多个种类,那 synchronized 代表的锁具体是把什么锁呢?

答案是—— Java 内置锁。在 Java 中,每个对象中都隐藏着一把锁,而 synchronized 关键字就是激活这把隐式锁的把手(开关)。

先来简单了解一下 synchronized,我们知道其共有 3 种使用方式:


Synchronized 的使用
  • 修饰静态方法:锁住当前 class,作用于该 class 的所有实例
  • 修饰非静态方法:只会锁住当前 class 的实例
  • 修饰代码块:该方法接受一个对象作为参数,锁住的即该对象

使用方法就不在这里赘述,可自行搜索其详细的用法,这不是本篇文章所关心的内容。

知道了 synchronized 的概念,回头来看标题,它说的锁升级到底是个啥?对于不太熟悉锁升级的人来说,可能会想:

所谓锁,不就是啪一下锁上就完事了吗?升级是个什么玩意?这跟打扑克牌也没关系啊。

对于熟悉的人来说,可能会想:

不就是「无锁 ==> 偏向锁 ==> 轻量级锁 ==> 重量级锁 」吗?

你可能在很多地方看到过上面描述的锁升级过程,也能直接背下来。但你真的知道无锁偏向锁轻量级锁重量级锁到底代表着什么吗?这些锁存储在哪里?以及什么情况下会使得锁向下一个 level 升级?

想知道答案,我们似乎必须先搞清楚 Java 内置锁,其内部结构是啥样的?内置锁又存放在哪里?

答案在开篇提到过——在 Java 对象中。

那么现在的问题就从「内置锁结构是啥」变成了「Java 对象长啥样」。

对象结构

宏观上看,Java 对象的结构很简单,分为三部分:


Java 对象结构

微观上看,各个部分都还可以深入展开,详见下图:


Java 详细对象结构

接下来分别深入讨论一下这三部分。

对象头

从脑图中可以看出,其由 Mark Word、Class Pointer、数组长度三个字段组成。简单来说:

  • Mark Word:主要用于存储自身运行时数据
  • Class Pointer:是指针,指向方法区中该 class 的对象,JVM 通过此字段来判断当前对象是哪个类的实例
  • 数组长度:当且仅当对象是数组时才会有该字段

Class Pointer 和数组长度没什么好说的,接下来重点聊聊 Mark Word。

Mark Word 所代表的「运行时数据」主要用来表示当前 Java 对象的线程锁状态以及 GC 的标志。而线程锁状态分别就是无锁、偏向锁、轻量级锁、重量级锁。

所以前文提到的这 4 个状态,其实就是 Java 内置锁的不同状态

在 JDK 1.6 之前,内置锁都是重量级锁,效率低下。效率低下表现在

而在 JDK 1.6 之后为了提高 synchronized 的效率,才引入了偏向锁轻量级锁

随着锁竞争逐渐激烈,其状态会按照「无锁 ==> 偏向锁 ==> 轻量级锁 ==> 重量级锁 」这个方向逐渐升级,并且不可逆,只能进行锁升级,而无法进行锁降级

接下来我们思考一个问题,既然 Mark Word 可以表示 4 种不同的锁状态,其内部到底是怎么区分的呢?(由于目前主流的 JVM 都是 64 位,所以我们只讨论 64 位的 Mark Word)接下来我们通过图片直观的感受一下。

(1)无锁


无锁

这个可以理解为单线程很快乐的运行,没有其他的线程来和其竞争。

(2)偏向锁


偏向锁

首先,什么叫偏向锁?举个例子,一段同步的代码,一直只被线程 A 访问,既然没有其他的线程来竞争,每次都要获取锁岂不是浪费资源?所以这种情况下线程 A 就会自动进入偏向锁的状态。

后续线程 A 再次访问同步代码时,不需要做任何的 check,直接执行(对该线程的「偏爱」),这样降低了获取锁的代价,提升了效率。

看到这里,你会发现无锁、偏向锁的 lock 标志位是一样的,即都是 01,这是因为无锁、偏向锁是靠字段 biased_lock 来区分的,0 代表没有使用偏向锁,1 代表启用了偏向锁。为什么要这么搞?你可以理解为无锁、偏向锁在本质上都可以理解为无锁(参考上面提到的线程 A 的状态),所以 lock 的标志位都是 01 是没毛病的。

PS:这里的线程 ID 是持有当前对象偏向锁的线程

(3)轻量级锁


轻量级锁

但是,一旦有第二个线程参与竞争,就会立即膨胀为轻量级锁。企图抢占的线程一开始会使用自旋

的方式去尝试获取锁。如果循环几次,其他的线程释放了锁,就不需要进行用户态到内核态的切换。虽然如此,但自旋需要占用很多 CPU 的资源(自行理解汽车空档疯狂踩油门)。如果另一个线程 一直不释放锁,难道它就在这一直空转下去吗?

当然不可能,JDK 1.7 之前是普通自旋,会设定一个最大的自旋次数,默认是 10 次,超过这个阈值就停止自旋。JDK 1.7 之后,引入了适应性自旋。简单来说就是:这次自旋获取到锁了,自旋的次数就会增加;这次自旋没拿到锁,自旋的次数就会减少

(4)重量级锁


重量级锁

上面提到,试图抢占的线程自旋达到阈值,就会停止自旋,那么此时锁就会膨胀成重量级锁。当其膨胀成重量级锁后,其他竞争的线程进来就不会自旋了,而是直接阻塞等待,并且 Mark Word 中的内容会变成一个监视器(monitor)对象,用来统一管理排队的线程。

这个 monitor 对象,每个对象都会关联一个。monitor 对象本质上是一个同步机制,保证了同时只有一个线程能够进入临界区,在 HotSpot 的虚拟机中,是由 C++ 类 ObjectMonitor 实现的。

那么 monitor 对象具体是如何来管理线程的?接下来我们看几个 ObjectMonitor 类关键的属性:

  • ContentionQueue:是个队列,所有竞争锁的线程都会先进入这个队列中,可以理解为线程的统一入口,进入的线程会阻塞。
  • EntryList:ContentionQueue 中有资格的线程会被移动到这里,相当于进行一轮初筛,进入的线程会阻塞。
  • Owner:拥有当前 monitor 对象的线程,即 —— 持有锁的那个线程。
  • OnDeck:与 Owner 线程进行竞争的线程,同一时刻只会有一个 OnDeck 线程在竞争。
  • WaitSet:当 Owner 线程调用 wait() 方法被阻塞之后,会被放到这里。当其被唤醒之后,会重新进入 EntryList 当中,这个集合的线程都会阻塞。
  • Count:用于实现可重入锁,synchronized 是可重入的。

对象体

对象体包含了当前对象的字段和值,在业务中u l是较为核心的部分。

对齐字节

就是单纯用于填充的字节,没有其他的业务含义。其目的是为了保证对象所占用的内存大小为 8 的倍数,因为HotSpot VM 的内存管理要求对象的起始地址必须是 8 的倍数。

锁升级

了解完 4 种锁状态之后,我们就可以整体的来看一下锁升级的过程了。

线程 A 进入 synchronized 开始抢锁,JVM 会判断当前是否是偏向锁的状态,如果是就会根据 Mark Word 中存储的线程 ID 来判断,当前线程 A 是否就是持有偏向锁的线程。如果是,则忽略 check,线程 A 直接执行临界区内的代码。

但如果 Mark Word 里的线程不是线程 A,就会通过自旋尝试获取锁,如果获取到了,就将 Mark Word 中的线程 ID 改为自己的;如果竞争失败,就会立马撤销偏向锁,膨胀为轻量级锁。

后续的竞争线程都会通过自旋来尝试获取锁,如果自旋成功那么锁的状态仍然是轻量级锁。然而如果竞争失败,锁会膨胀为重量级锁,后续等待的竞争的线程都会被阻塞。


锁升级过程

EOF

其实偏向锁还有一个撤销的过程,也是有代价的,但相比于偏向锁带好的好处,是能够接受的。但我们这里重点的还是关注锁升级的具体逻辑和细节,关于锁升级的过程就聊到这里。

欢迎 wx 搜索关注 「SH的全栈笔记」

- END -

详细了解 synchronized 锁升级过程的更多相关文章

  1. 并发编程:synchronized 锁升级过程的验证

        关于synchronized关键字以及偏向锁.轻量级锁.重量级锁的介绍广大网友已经给出了太多文章和例子,这里就不再重复了,也可点击链接来回顾一下.在这里来实战操作一把,验证JVM是怎么一步一步 ...

  2. synchronized锁升级详细过程

    java对象头由3部分组成: 1.Mark Word 2.指向类对象(对象的class对象)的指针 3.数组长度(数组类型才有) 重点是 Mark Word结构,下面以32位HotSpot为例: 一. ...

  3. 关于Synchronized的偏向锁,轻量级锁,重量级锁,锁升级过程,自旋优化,你该了解这些

    前言 相信大部分开发人员,或多或少都看过或写过并发编程的代码.并发关键字除了Synchronized(如有不懂请移至传送门,关于Synchronized的偏向锁,轻量级锁,重量级锁,锁升级过程,自旋优 ...

  4. Synchronized锁升级原理与过程深入剖析

    Synchronized锁升级原理与过程深入剖析 前言 在上篇文章深入学习Synchronized各种使用方法当中我们仔细介绍了在各种情况下该如何使用synchronized关键字.因为在我们写的程序 ...

  5. 再谈synchronized锁升级

    在图文详解Java对象内存布局这篇文章中,在研究对象头时我们了解了synchronized锁升级的过程,由于篇幅有限,对锁升级的过程介绍的比较简略,本文在上一篇的基础上,来详细研究一下锁升级的过程以及 ...

  6. Synchronized锁升级

    Synchronized锁升级 锁的4中状态:无锁状态.偏向锁状态.轻量级锁状态.重量级锁状态(级别从低到高) 为什么要引入偏向锁? 因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞 ...

  7. 深入并发锁,解析Synchronized锁升级

    这篇文章分为六个部分,不同特性的锁分类,并发锁的不同设计,Synchronized中的锁升级,ReentrantLock和ReadWriteLock的应用,帮助你梳理 Java 并发锁及相关的操作. ...

  8. JAVA对象分析之偏向锁、轻量级锁、重量级锁升级过程

    在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分: 对象头(Header) 实例数据(Instance Data) 对齐填充(Padding). 对象头 HotSpot虚拟机(后面 ...

  9. Synchronized用法原理和锁优化升级过程(面试)

    简介 多线程一直是面试中的重点和难点,无论你现在处于啥级别段位,对synchronized关键字的学习避免不了,这是我的心得体会.下面咱们以面试的思维来对synchronized做一个系统的描述,如果 ...

随机推荐

  1. 学习MyBatis必知必会(5)~了解myBatis的作用域和生命周期并抽取工具类MyBatisUtil、mybatis执行增删改查操作

    一.了解myBatis的作用域和生命周期[错误的使用会导致非常严重的并发问题] (1)SqlSessionFactoryBuilder [ 作用:仅仅是用来创建SqlSessionFactory,作用 ...

  2. CF 1394 简要题解

    最近都会做一些 \(\rm Div1\) 套题中 \(3000\) 分以下的题目. A 直接枚举贪心即可. B 首先不难发现总共可能的 \(c\) 序列只有 \(k!\) 种,很明显要暴力枚举所有情况 ...

  3. AT2402 [ARC072D] Dam

    首先我们可以将 \(t_i \times v_i\) 看作一个整体,不妨令 \(x_i = v_i, y_i = t_i \times v_i\) 这样两堆水混合后相当于将两个维度相加,方便了计算. ...

  4. JavaCV的摄像头实战之六:保存为mp4文件(有声音)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. Spring学习一: Ioc容器

    Spring 容器:      Spring 容器是Spring框架的核心.Spring容器将创建Bean对象实例,把它们联系在一起,配置它们,并管理它们整个生命周期从创建到销毁.Spring 容器通 ...

  6. 物理CPU,物理核,逻辑CPU,虚拟CPU(vCPU)区别 (转)

    在做虚拟化时候,遇到划分CPU的问题,因此考虑到CPU不知道具体怎么划分,查询一些资料后就写成本文. a. 物理CPU:物理CPU是相对于虚拟CPU而言的概念,指实际存在的处理器,就是我们可以看的见, ...

  7. IDEA中Git的一般使用场景

    感谢大佬:https://www.cnblogs.com/javabg/p/8567790.html 工作中多人使用版本控制软件协作开发,常见的应用场景归纳如下: 假设小组中有两个人,组长小张,组员小 ...

  8. Ajax接收服务器返回的信息response

    Ajax可以向服务器发起请求,有去的方式,那么久必然可疑返回. 服务器返回的信息也可以通过Ajax接收. Ajax共有5种状态: 1.创建对象,没有调用open方法 2.对象发起请求http,已经调用 ...

  9. UIPickView的基本使用

    UIPickView和TableView一样,想要展示数据也要设置数据源和代理设置数据源self.pickView.dataSource = self;设置代理self.pickView.delega ...

  10. 红色小圆点+数字的badge自定义小方法 by Nicky.Tsui

    效果如图. 实现方法比较简单,在view上增加一个label label设置: 1 badgeLabel = [[UILabel alloc]initWithFrame:CGRectMake(CGRe ...