【JUC2022】第六章 Synchronized 与锁升级
创始人
2024-05-29 08:22:34

【JUC2022】第六章 Synchronized 与锁升级

文章目录

  • 【JUC2022】第六章 Synchronized 与锁升级
  • 一、对象内存布局
  • 二、锁升级
    • 1.synchronized 锁优化的背景
    • 2.无锁状态
    • 3.偏向锁
    • 4.轻量级锁
    • 5.重量级锁
    • 6.锁升级过程中 Mark Word 的变化
  • 三、JIT 对锁的优化

一、对象内存布局

回顾【JVM】第二章 JVM类加载、JVM对象

二、锁升级

1.synchronized 锁优化的背景

用锁能够实现数据的安全性,但是会带来性能下降

无锁能够实现基于线程并行,提升程序性能,但是会降低安全性

因此,我们需要在二者之间寻求一个平衡

锁升级过程

无锁→偏向锁→轻量级锁→重量级锁 偏向锁:Mark Word 存储的是指向偏向的线程 ID
轻量级锁:Mark Word 存储的是指向线程栈中 Lock Record 的指针
重量级锁:Mark Word 存储的是指向堆中的 monitor 对象的指针

JDK1.5 及之前的 synchronized

Java 的线程是映射到操作系统原生线程之上的,如果要阻塞或者唤醒一个线程,就需要操作系统接入,需要在用户态与内核态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自的内存空间和寄存器等。用户态切换至内核态需要传递很多变量和参数,内核态也需要保护好用户态在切换时的一些寄存器值和变量,以便内核态调用结束后切换回用户态继续工作

在 JDK1.5 及之前版本,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)依赖于操作系统的 Mutex Lock(系统互斥量)来实现的,挂起线程和恢复线程都需要切换到内核态,阻塞或者唤醒一个 Java 线程需要操作系统切换 CPU 状态来完成,这种切换需要耗费处理器时间

JDK1.6 为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁

2.无锁状态

Mark Word 偏向锁位为 0,锁标志位为 01

3.偏向锁

Mark Word 偏向锁位为 1,锁标志位为 01,并且有 54 位用于存放指向偏向的线程 ID

在大部分多线程的情况下,锁不仅不存在多线程竞争,还存在锁由同一个线程多次获得的情况。当同一段同步代码一直被同一个线程多次访问,由于只有一个线程,那么该线程在后续访问时便会自动获得锁

4.轻量级锁

Mark Word 没有偏向锁位,锁标志位为 00,并且有 62 位用于存放指向线程栈中 Lock Record 的指针

当在偏向锁状态下,第二个线程并没有成功获取锁,那么就会升级为轻量级锁,此时轻量级锁由第一个线程继续持有。轻量级锁是为了在线程近乎交替执行同步代码块的时候提高性能,原理是通过 CAS 获取锁

JVM 会为每个线程在当前线程的栈帧中创建用于存储锁记录的空间,官方称之为 Displaced Mark Word。若一个线程获得锁时发现是轻量级锁,会把锁的 Mark Word 复制到自己的 Displaced Mark Word 里面。然后线程尝试用 CAS 将锁的 Mark Word 替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示 Mark Word 已经被替换成了其他线程的锁记录,说明在与其他线程竞争,当前线程就尝试使用自旋来获取锁

当自旋超过某个次数,就会升级为重量级锁,并且自旋的次数是自适应的。线程如果自旋成功了,那么下次自旋的最大次数会增加,因为 JVM 认为既然上次成功了,那么下次也很大概率会成功;繁殖,如果自旋失败,那么下次会减少自旋次数,避免 CPU 空转

5.重量级锁

Mark Word 没有偏向锁位,锁标志位为 10,并且有 62 位用于存放指向互斥量的指针

Java 中 synchronized 的重量级锁,是基于进入和退出 monitor 对象实现的。在编译时会将同步代码块的开始位置插入 monitor enter 指令,在结束位置插入 monitor exit 指令。当线程执行到 monitor enter 指令时,会尝试获取对象所对应的 Monitor 所有权,如果获取到了,即获取到了锁,会在 monitor 的 owner 中存放当前线程的 id,这样它将处于锁定状态,除非退出同步块,执行 monitor exit 命令,否则其他线程无法获取到这个 monitor

6.锁升级过程中 Mark Word 的变化

锁升级为轻量级锁或重量级锁后,Mark Word 中保存的分别是线程栈帧里的锁记录指针和重量级锁指针,已经没有位置再保存哈希码、GC 年龄了,那么这些信息去哪了?

当一个对象已经计算过一致性哈希码后,就再也无法进入偏向锁状态了,而是直接进入轻量级锁状态,将哈希码和 GC 年龄保存在线程栈帧的锁记录(Lock Record)中。当一个对象正处于偏向锁状态,又收到需要计算其一致性哈希码请求时,它会立即膨胀为重量级锁。在重量级锁的实现中,对象头指向了重量级锁的位置,代表重量级锁的 ObjectMonitor 类里有字段可以记录非加锁状态下的 Mark Word,其中自然可以存储原来的哈希码

三、JIT 对锁的优化

JIT(Just In Time Compiler,即时编译器)

锁消除
如果使用的锁对象是每个线程都会 new 出来的,那么加的这把锁就毫无意义,因为线程根本不需要和其他线程竞争,因此 JIT 编译器会无视这个加锁

锁粗化
假如方法中前后相邻的同步代码块中用的都是同一个锁对象,那么 JIT 编译器就会把这个同步代码块合并成一个大块

相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...