偏向锁、轻量级所、自旋锁、重量级锁,它们都是什么?它们之间有什么关系?为什么会有这些锁?

news2024/10/2 3:26:15

互斥锁的本质是共享资源。
当有多个线程同时对一个资源进行操作时,为了线程安全,要对资源加锁。
更多基础内容参看上文《深入了解Java线程锁(一)》

接下来,我们来看看两个线程抢占重量级锁的情形:
在这里插入图片描述

上图讲述了两个线程ThreadA和ThreadB两个线程在争抢锁,
ThreadA进入来临界区,可以执行资源代码;
ThreadB没有抢到锁,进入BLOCKED队列,等待唤醒。
当ThreadA执行完后,释放锁,去唤醒ThreadB。
这是重量级锁的流程。
因为它会带来阻塞和唤醒,会带来开销,所以叫“重量级”锁。


加锁会带来性能开销,Java 1.6之前只有重量级锁。
Java 1.6 之后做了优化,出现了“轻量级锁 和 偏向锁”,能够减少重量级锁的获得和释放的性能开销。

我们在线程竞争的情况下,才会用重量级锁来处理。
我们是否能在加重量级锁之前,先去试着判断一下,是否存在竞争呢?
如果不存在,就不必使用重量级锁了,就可以带来性能优化了。


锁的升级
在这里插入图片描述
ThreadA 和ThreadB 加锁不是一步到位直接重量级锁。
而是先通过偏向锁抢占,当A获取来偏向锁后,B去抢占;
如果不成功,B会升级到轻量级锁继续尝试抢占;
如果不成功,B会去重量级锁抢占;
如果不成功,进入阻塞队列,等待唤醒。

线程最终的结果是要实现互斥的特性,我们希望能在线程B阻塞之前抢占到锁,如果可以,那么就不需要进行重量级锁的BLOCKED和唤醒,就会提高性能。

加锁一定会带来性能开销,怎么优化?—— 不加锁!(在不加锁的情况下解决线程安全问题)
锁的升级以及优化的目的,就是如何能在线程B不阻塞的情况下也能抢占到锁。

偏向锁

偏向锁是偏向的特性。
如果我们某个线程获取的锁是偏向锁,在对象头中会存储线程ID,偏向锁标志为1
在这里插入图片描述
偏向锁是不存在竞争的情况下可以使用。
名字叫做“偏向”就是当前偏向于某个线程,就将其线程ID存放在对象头中。
这种情况(加了锁但是没有竞争)比较少,所以默认情况下,偏向锁是关闭的。


我们来看 偏向锁的获得和撤销流程图
在这里插入图片描述
图中有两个线程A和B,中间是对象头。

  1. 线程A去访问同步代码块的时候,会检查对象头中是否存储了线程A,如果说没有存储的话,它会通过CAS替换对象头,把线程A的ID存储到对象头中。

CAS 相当于是一个乐观锁的概念,会去比较预期数据和原始数据是否一致,如果一致则修改,不一致则修改失败。CAS是原子性的操作。

  1. 替换成功的话,对象头中会存储A线程的ID和锁的标记 101

我们来实际看一下:
因为偏向锁默认关系,所以要先打开偏向锁:
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
在这里插入图片描述

执行没有线程抢占的代码:(与此代码相关背景请见《深入了解JAVA线程锁(一)》)

import org.openjdk.jol.info.ClassLayout;

public class MyClassLayout {
    public static void main(String[] args) {
        MyClassLayout myClassLayout = new MyClassLayout();
        synchronized (myClassLayout) {
            System.out.println(ClassLayout.parseInstance(myClassLayout).toPrintable());
        }
    }
}

执行结果为:

demo.MyClassLayout object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 a0 c5 45 (00000101 10100000 11000101 01000101) (1170579461)
      4     4        (object header)                           cb 01 00 00 (11001011 00000001 00000000 00000000) (459)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

我们可以看到当前是 101,是偏向锁。


  1. 线程A可以执行同步代码块。
  2. 此时如果有线程B在线程A已经获得偏向锁的情况下,访问同步代码块。
  3. 检查对象头中是否存储了线程B,如果没有的话,使用CAS替换对象头,可能成果也可能失败。
  4. 如果替换失败的话,线程B就要撤销原来A获得的偏向锁。
  5. 线程B暂停线程A,进行撤销。这里的撤销有两种,一种是撤销,一种是升级。
  6. 如果撤销成果,则线程B会获得偏向锁。什么情况可以成功撤销:
    ① 线程A已经执行结束;
    ② 线程A处于全局安全点,表示已经没有指令在运行。
  7. 如果有线程A的指令在执行的话,就无法撤销(即撤销失败)。那么就会升级到轻量级锁,会把对象头标记改为轻量级锁的标记。

偏向锁中没有阻塞,只有一个CAS。
轻量级锁中也没有阻塞,而是有个重试,即多次CAS(自旋锁)。

我们来看轻量级锁的流程图
在这里插入图片描述

  1. 同时两个线程A、B过来。
  2. 线程A进来访问同步代码块,会在线程的栈针中分配一个rock record空间,把对象头复制到rock record空间里。
    把当前的指针,指向栈中锁记录。
    在这里插入图片描述
  3. 抢占锁在轻量级锁范围内所做的事就是通过CAS修改 Mark Word中指向 lock record 的指针。 如果成功,表示当前线程A获得轻量级锁,它会把对象头存储指向栈中记录的指针。状态是 00
  4. 因为当前锁已被线程A获得,线程B竞争失败,它会有重试机制。(轻量级锁的多次CAS就是体现中这里,它叫自旋锁)

很多情况下,线程获得锁再释放锁的时间很短。这种情况下,我们通过重试去抢占,相比通过阻塞去抢占,更划算,所以通过重试去达到抢占锁的目的。
但是重试也是有次数的,不能无限重试。在JDK1.6之前,自旋锁默认重试10次。JDK1.6之后,引入来自适应自旋锁,会根据上一次抢占锁时间长,这次自适应时间长一些,否则会短一些。

  1. 如果线程B自旋失败,就会锁膨胀,升级为重量级锁。

Synchronized是个非公平锁,允许插队。

在这里插入图片描述
5. 重量级锁有个 Monitor 监视器,线程A、B会进行 monitor enter的抢占,
6. 如果线程A在monitor enter中抢占成功,就会持有锁对象,可以执行资源代码块。
7. 这时,没有抢到锁的线程B,会阻塞,然后放入到队列中。
8. 当线程A执行完资源代码释放后,阻塞的线程B会被唤醒,会进行新一轮的抢占。


我们通过代码来看锁膨胀的示例:

public class MyLockDemo {
    public static void main(String[] args) throws InterruptedException {
        MyLockDemo myLockDemo = new MyLockDemo();
        new Thread(()->{
            synchronized (myLockDemo){
                System.out.println("Thread-A caught the lock.");
                System.out.println(ClassLayout.parseInstance(myLockDemo).toPrintable());
            }
        },"Thread-A").start();

        //sleep 10秒,则是轻量级锁
        Thread.sleep(10000);

        synchronized (myLockDemo){
            System.out.println("main is catching.");
            System.out.println(ClassLayout.parseInstance(myLockDemo).toPrintable());
        }
    }
}

执行上述代码,结果为:

Thread-A caught the lock.
LockDemo.MyLockDemo object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           d0 f1 1f 62 (11010000 11110001 00011111 01100010) (1646260688)
      4     4        (object header)                           1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

main is catching.
LockDemo.MyLockDemo object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           38 f7 af 60 (00111000 11110111 10101111 01100000) (1622144824)
      4     4        (object header)                           1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


Process finished with exit code 0

我们可以看到最末三位是 000,所以是轻量级锁。
接下来,我们把代码中的sleep 注释掉,再看结果:

main is catching.
LockDemo.MyLockDemo object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           0a a1 70 b3 (00001010 10100001 01110000 10110011) (-1284464374)
      4     4        (object header)                           a8 02 00 00 (10101000 00000010 00000000 00000000) (680)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Thread-A caught the lock.
LockDemo.MyLockDemo object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           0a a1 70 b3 (00001010 10100001 01110000 10110011) (-1284464374)
      4     4        (object header)                           a8 02 00 00 (10101000 00000010 00000000 00000000) (680)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


Process finished with exit code 0

这时我们发现末尾三位是 010,是重量级锁无疑了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/369988.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JDBC-

文章目录JDBC1,JDBC概述1.1 JDBC概念1.2 JDBC本质1.3 JDBC好处2,JDBC快速入门2.1 编写代码步骤2.2 具体操作3,JDBC API详解3.1 DriverManager3.2 Connection (事务归我管)3.2.1 获取执行对象3.2.2 事务管理3.3 Stateme…

CSS 浮动【快速掌握知识点】

目录 前言 一、设置浮动属性 二、确定浮动元素的宽度 三、清除浮动 总结: 前言 CSS浮动是一种布局技术,它允许元素浮动到其父元素的左侧或右侧,从而腾出空间给其他元素。 一、设置浮动属性 使用CSS float属性将元素设置为浮动。例如&…

【华为OD机试模拟题】用 C++ 实现 - 数组的中心位置(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

MySQL —— 表的约束

文章目录1. null 空属性2. default 默认值3. comment 列描述4. zerofill 格式化输出5. primary key 主键6. auto_increment 自增长7. 唯一键8. unique key 外键前言: 表的约束主要是靠数据类型。有些情况,光靠数据类型约束是不够的,比如想要限…

【Java】ThreadLocal原理

​ ThreadLocal ThreadLocal意为线程本地变量,用于解决多线程并发时访问共享变量的问题。 每个线程都会有属于自己的本地内存,在堆(也就是上图的主内存)中的变量在被线程使用的时候会被复制一个副本线程的本地内存中&#xff0c…

【H5 | CSS | JS】如何实现网页打字机效果?快收下这份超详细指南(附源码)

💂作者简介: THUNDER王,一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读,同时任汉硕云(广东)科技有限公司ABAP开发顾问。在学习工作中,我通常使用偏后…

在C#中初测OpencvSharp4

一、配置OpenCV 首先,我们新建一个工程,然后就是给这个工程配置OpenCV了,最简单的方法还是Nuget,来我们右键一个Nuget: 打开Nuget后,你可以直接输入OpenCVSharp4来查找,当然,如果你…

公司新来的00后真是卷王,工作没两年,跳槽到我们公司起薪20K都快接近我了

都说00后躺平了,但是有一说一,该卷的还是卷。这不,前段时间我们公司来了个00后,工作都没两年,跳槽到我们公司起薪18K,都快接近我了。后来才知道人家是个卷王,从早干到晚就差搬张床到工位睡觉了。…

罗永浩进场之后,苹果入局之前:XR又寒冬了吗?

科技圈的悲欢并不相通。ChatGPT狂飙之际,XR领域正在迎来至暗时刻。岁末年初,就在罗永浩重返高科技创业,计划进军XR(扩展现实)类领域的时间段前后,接连出现了押注元宇宙的Meta裁员,Meta旗下VR工作室Ready At…

【华为OD机试模拟题】用 C++ 实现 - 快递业务站(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

【华为OD机试模拟题】用 C++ 实现 - 流水线(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 分积木(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 吃火锅(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - RSA 加密算法(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 构成的正方形数量(2023.Q1) 【华为OD机试模拟…

数据库|(六)连接查询

(六)连接查询1. 笛卡尔乘积2. 连接查询分类2.1 按年代分2.2 按功能分3. 等值连接(sql 92标准)3.1 特点3.2 一般使用3.3 为表取别名3.4 两表顺序可以调换3.5 可以加筛选3.6 可以加分组3.7 可以加排序3.8 可以实现三表连接4. 非等值连接(sql 92标准)5. sql…

【深度学习】GPT系列模型:语言理解能力的革新

GPT-1🏡 自然语言理解包括一系列不同的任务,例如文本蕴涵、问答、语义相似度评估和文档分类。尽管大量的未标记文本语料库很充足,但用于学习这些特定任务的标记数据却很稀缺,使得判别式训练模型难以达到良好的表现。我们证明&…

Spring(入门)

1. 什么是spring,它能够做什么?2. 什么是控制反转(或依赖注入)3. AOP的关键概念4. 示例 4.1 创建工程4.2 pom文件4.3 spring配置文件4.4 示例代码 4.4.1 示例14.4.2 示例2 (abstract,parent示例)4.4.3 使用有参数构造方法创建jav…

【华为OD机试模拟题】用 C++ 实现 - 找数字(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

显示器管理工具:BetterDisplay Pro Mac

BetterDisplay Pro Mac 是一个非常棒的工具!它可以让您将显示器转换为完全可缩放的屏幕,允许亮度控制,提供 XDR/HDR 升级(在兼容显示器上超过 100% 的额外亮度),完全调光为黑色,帮助您为 Mac 创…

PRML笔记3-绪论中最小化错误分类率的理解

这个小节的内容很少,因为自己数学水平实在太差,所以理解不到之处还请批评指正。 在分类任务中我们希望尽可能减少错误的分类,例如我们有一些病人的临床数据,希望通过这些临床数据对患者的诊断提供帮助,比如根据临床数据…

gazebo仿真环境中添加robotiq 2f 140的gripper_controller控制器

gazebo仿真环境中添加robotiq 2f 140的gripper_controller控制器 搭建环境: ubuntu: 20.04 ros: Nonetic sensor: robotiq_ft300 gripper: robotiq_2f_140_gripper UR: UR3 reasense: D435i 通过下面几篇博客配置好了ur3、力传感器、robotiq夹爪、rea…

vuejs文件传参方式

vue2版本 vscode 大致总结一下几种情况: 1.父传子,父组件利用props向子组件传递数据 1.在父组件的子组件标签上绑定一个属性,挂载要传输的变量 2.在子组件中通过props来接受数据,props可以是数组也可以是对象,接受的数…

RayVentory updated

RayVentory updated RayVentory Aspera连接器12.5.0.11850[更新1] 增加了对Docker容器的支持。 已将Xen虚拟机的设备类型更改为“Virtual_Machine”。 RayVentory扫描引擎12.5.3579.69[更新1] 添加了法语UI翻译预览。 RayVentory提供了硬件和软件的全面清单,并提供了…