【JUC】二十九、synchronized锁升级之轻量锁与重量锁

news2024/12/23 22:37:33

文章目录

  • 1、轻量锁
  • 2、轻量锁的作用
  • 3、轻量锁的加锁和释放
  • 4、轻量级锁的代码演示
  • 5、重量级锁
  • 6、重量级锁的原理
  • 7、锁升级和hashcode的关系
  • 8、锁升级和hashcode关系的代码证明
  • 9、synchronized锁升级的总结
  • 10、JIT编译器对锁的优化:锁消除和锁粗化
  • 11、结语

📕相关笔记:

【synchronized锁升级之 无锁】
【synchronized锁升级之 偏向锁】

1、轻量锁

前面一篇提到偏向锁,即只有一个线程在竞争,此时通过资源类对象的对象头的Mark Word来标记,避免了用户态和内核态的频繁切换。

在这里插入图片描述

再往下,又来了一个线程也来竞争这个锁,且此时这两个线程近乎可以错开交替执行(或者说同步代码块/方法执行一次时间很短,哪怕另一个线程等,也不会等太久),如下图的1、2、3、4标号:

在这里插入图片描述

这就是轻量级锁的出现场景:有线程来参与竞争了,但不存在锁竞争太过激烈的情况,获取锁的冲突时间极端,本质就是CAS自旋锁,不要直接往重锁走。对应的共享对象内存图:

在这里插入图片描述

2、轻量锁的作用

轻量锁是为了在两个线程近乎交替执行同步块时来提高性能。

直白说就是先CAS自旋,不行了再考虑升级为重锁,使用操作系统的互斥量。升级到轻量锁的时机有:

  • 关闭了偏向锁
  • 多线程竞争偏向锁,可能导致偏向锁升级为轻量锁(这里写可能,是因为如果恰好是一个线程over,一个线程上位,则依旧是偏向锁)

举个例子:比如现有A线程拿到了锁,A一个人走偏向锁玩了一会儿后,线程B来了,B在争抢时发现共享对象的对象头中Mark Word里的线程ID标记不是线程B的ID(而是线程A),此时,B线程通过CAS来尝试修改标记。当:

  • 此时线程A刚好Over,B上位,修改Mark Word里的线程ID为B,此时,仍为偏向锁,且偏向B

在这里插入图片描述

  • 如果A正在执行,B修改失败,则升级为轻量级锁,且轻量级锁继续由原来的线程A持有,接着执行刚才没执行完的,而线程B则自旋等待获取这个轻量级锁

在这里插入图片描述

3、轻量锁的加锁和释放

加锁:

JVM会在线程的栈帧中创建用于存储锁记录Lock Record的空间,称为Displaced Mark Word。

在这里插入图片描述

若一个线程获得锁时发现是轻量级锁,会把对象锁的MarkWord复制到自己的Displaced Mak Word里面。然后线程尝试用CAS将锁的MarkWord替换为指向锁记录的指针。如下面两幅草图示意的变化过程:

在这里插入图片描述

在这里插入图片描述

如果替换成功,当前线程获得轻量锁。如果失败,表示Mark Word已经被替换成了其他线程的锁记录,说明在与其它线程竞争锁,当前线程就尝试使用自旋来获取锁(自旋一定次数后仍未获得锁,升级为重量锁)。

轻量级锁的释放:

在释放锁时,当前线程会使里CAS操作将Displaced Mark Word的内容复制回对象锁的Mark Word里面。如果没有发生竞争。那么这个复制的操作会成功。如果持有锁期间有其他线程因为自旋多次导致轻量级锁升级成了重量级锁,那么CAS操作会失败,此时会释放锁并唤醒被阻塞的线程。

4、轻量级锁的代码演示

-XX:-UseBiasedLocking

添加JVM参数,关闭偏向锁,就可以直接进入轻量级锁:

Object object = new Object();
new Thread(() -> {
    synchronized (object){
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}).start();

运行:

在这里插入图片描述

轻量锁下,自旋达到一定次数或者说程度,会升级为重量锁:

  • Java6之前,默认情况下自旋的次数是10次或者自旋的线程数超过了cpu核数的一半,可-XX:PreBlockSpin=10来修改
  • Java6之后,JVM做了优化,采用自适应自旋

自适应自旋,即线程如果自旋成功了,那下次自旋的最大次数会增加,因为JVM认为既然上次成功了,那么这一次也很大概率会成功。反之,如果很少会自旋成功,那么下次会减少自旋的次数甚至不自旋,以避免CPU空转。直白说就是会总结前人的经验了、会预判走位了。

轻量锁与偏向锁的区别:

  • 偏向锁是一个线程自己在玩,而偏向锁涉及竞争,且争夺轻量级锁失败时,自旋尝试抢占锁
  • 轻量级锁每次退出同步块都需要释放锁(要不就不会是一个走了一个接上了),而偏向锁则只在有线程来竞争时才释放锁

5、重量级锁

竞争太激烈时,只能捅到重量级锁,进行内核态和用户态的切换,但前面偏向锁和轻量级锁已然做了一定程度的缓冲和优化了。

在这里插入图片描述

有大量的线程参与锁的竞争,冲突性很高:

Object object = new Object();
//多个线程
for (int i = 0; i < 6; i++) {
    new Thread(() -> {
        synchronized (object){
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    },String.valueOf(i)).start();
}

运行:

在这里插入图片描述

6、重量级锁的原理

Java中synchronized的重量级锁,是基于进入和退出Monitor对象实现的。在编译时会将同步块的开始位置插入monitor enter指令,在结束位置插入monitor exit指令。

当线程执行到monitor enter指令时,会尝试获取对象所对应的Monitor所有权,如果获取到了,即获取到了锁,会在Monitor的owner中存放当前线程的id,这样它将处于锁定状态,除非退出同步块,否则其他线程无法获取到这个Monitor。

7、锁升级和hashcode的关系

在这里插入图片描述

可以看到,无锁状态下,Java对象头的Mark Word中是有空间存hashcode的,锁升级后,则没有位置了,那要是锁升级后hashcode去哪儿了 ?

在这里插入图片描述

总结下:

1) 在无锁状态下,Mark Word可以存储对象的identity hash code值,当对象的hashCode()方法第一次被调用时,JVM会生成对应的identity hash code值,存于对象头的Mark Word中。

2) 对于偏向锁,在线程获取偏向锁时,用Thread Id和epoch值(看成时间戳)去覆盖identity hash code所在的位置。如果一个对象的hashcode()方法已经被调用过一次,则这个对象不能被设置偏向锁,因为如果可以,那identity hash code就会被线程ID覆盖,就会造成同一对象,前后两次调用hashcode方法得到的结果不一致。

3) 升级为轻量锁时,JVM会在当前线程的栈帧中创建一个锁记录空间Lock Record(前面已提到),用于拷贝和存储锁对象的Mark Word,里面自然包含了identity hash code、GC年龄等,且释放轻量锁时,这些数据又会写回对象头,因此轻量级锁可以和identity hash code共存。

4) 到重量级锁时,Mark Word保存的是重量级锁指针,而代表重量级锁的ObiectMonitor类里有字段记录了非加锁状态下的Mark Word,锁释放以后也会写回对象头。

8、锁升级和hashcode关系的代码证明

Case1:当一个对象已经计算过identity hashcode,它就无法进入偏向锁状态,会跳过偏向锁,直接升级轻量级锁

//先睡5秒,抵消偏向锁开启的延时,保证开启偏向锁
TimeUnit.SECONDS.sleep(5);
Object object = new Object();
System.out.println("这里应该是偏向锁==>");
System.out.println(ClassLayout.parseInstance(object).toPrintable());
//没有重写hashcode,重写后无效
int hashCode = object.hashCode();
//验证当一个对象已经计算过identity hash code后,就无法进入偏向状态
new Thread(() -> {
    synchronized (object){
        System.out.println("这里本应是偏向锁,但刚才计算过一致性哈希hashcode,这里会直接升级为轻量级锁 ==>");
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}).start();

在这里插入图片描述

Case2:偏向锁过程中遇到一致性哈希计算请求,立马撤销偏向模式,膨胀为重量级锁

//先睡5秒,抵消偏向锁开启的延时,保证开启偏向锁
TimeUnit.SECONDS.sleep(5);
Object object = new Object();
synchronized (object){
    System.out.println(ClassLayout.parseInstance(object).toPrintable());
    System.out.println("此时是偏向锁,但下面一计算哈希,会立马撤销偏向模式,膨胀为重量级锁");
    //计算哈希值,这里的hashcode方法是没有重写过的
    int hashCode = object.hashCode();
    System.out.println(ClassLayout.parseInstance(object).toPrintable());
}

在这里插入图片描述

9、synchronized锁升级的总结

在这里插入图片描述

synchronized锁升级,目的还是实现一个性能优化,思想就是:先自旋,不行了再阻塞。一直都是围绕尽量避免内核态和用户态频繁切换来展开的。实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式。太精辟了这句!道出了这几种锁的关系。

在这里插入图片描述

另外,synchronized在修饰方法和代码块时,在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的。JDK1.6之前synchronized使用的是重量级锁,JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁。

请添加图片描述
最后 :

  • 偏向锁:适用于单线程的情况,在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁。
  • 轻量级锁:适用于竞争较不激烈的情况(这和乐观锁的使用范围类似),轻量级锁采用的是自旋锁,如果同步方法/代码块执行时间很短的话(就很容易一个线程完事儿了,另一个线程尚未,哪怕不是这么刚刚好,也自旋等不了太久),采用轻量级锁自旋虽然会占用Cpu资源,但是相对比使用重量级锁还是更高效。
  • 重量级锁:适用于竞争激烈的情况,如果同步方法/代码块执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁一更严重,这时候就需要升级为重量级锁。

10、JIT编译器对锁的优化:锁消除和锁粗化

JIT,即Just Time Compiler,翻译:即时编译器。

synchronized锁消除

以下是一个简单的synchronized代码,没啥毛病(别说优化成线程池):

public class LockClearDemo {

    static Object objectLock = new Object();

    public void m1(){
        synchronized (objectLock){
            System.out.println("----hello clearDemo");
        }
    }
    public static void main(String[] args) {
        LockClearDemo lockClearDemo = new LockClearDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                lockClearDemo.m1();
            },String.valueOf(i)).start();
        }
    }
}

但此时,做出这样一个修改:

在这里插入图片描述

这么写,看似有synchronized,语法也没报错,实际每个线程进来都new一个自己的object对象,相当于是每一个线程一个自己创造的锁,而不是正常的所有线程共同抢一个对象的锁,因此,这么写毫无意义,JIT编译器会无视它,极端的说就是根本没有加这个锁对象的底层的机器码,是消除了锁的使用。

synchronized锁粗化

看示例代码:

public class LockDemo {

    static Object objectLock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (objectLock){
                System.out.println("111111");
            }
            synchronized (objectLock){
                System.out.println("222222");
            }
            synchronized (objectLock){
                System.out.println("333333");
            }
            synchronized (objectLock){
                System.out.println("444444");
            }
        }).start();
    }
    
}

注意,这不是可重入锁,这里是频繁加锁解锁。虽然无语法错误,但底层编译器会把它合并优化为:

在这里插入图片描述

锁粗化:假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块,加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能。

11、结语

  • 没有锁:自由自在
  • 偏向锁:唯我独尊
  • 轻量锁:楚汉争霸
  • 重量锁:群雄逐鹿

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

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

相关文章

Mac 如何删除文件及文件夹?可以尝试使用终端进行删除

MacOS 是 Mac 电脑采用的操作系统&#xff0c;你知道 Mac 如何删除文件吗&#xff1f;除了直接将文件或者文件夹拖入废纸篓之外&#xff0c;我们还可以采用终端命令的办法去删除文件&#xff0c;本文为大家总结了 Mac 删除文件方法。 为何使用命令行删除文件 在使用 Mac 电脑…

纯前端使用XLSX导出excel表格

1 单个sheet page.js(页面中的导出方法) import { exportExcel } from ../../../utils/exportExcel.js; leadOut() {const arr [{ id: 1, name: 张三, age: 14, sex: 男 },{ id: 2, name: 李四, age: 15, sex: 女 },{ id: 3, name: 王五, age: 16, sex: 男 },];const allR…

6.4.如何生成SDP

在我们前面的课程中呢&#xff0c;我已经向你介绍了很多sdp相关的内容&#xff0c;那今天呢&#xff0c;我们来看看如何生成sdp。 那我们的目标呢&#xff0c;当然就是生成这样一个文本&#xff0c;那这个文本呢&#xff0c;就是sdp文本那里边包括了各种各样的媒体信息&#…

GPIO模拟MDIO

背景 CPU&#xff1a;AST2500 驱动里实现GPIO模拟MDIO驱动,参考内核驱动mdio-bitbang.c和mdio-gpio.c&#xff0c;当前项目不支持设备树&#xff0c;驱动需要改成platform注册 MDIO介绍 SMI接口 SMI是MAC内核访问PHY寄存器接口&#xff0c;它由两根线组成&#xff0c;双工…

论文阅读《Parameterized Cost Volume for Stereo Matching》

论文地址&#xff1a;https://openaccess.thecvf.com/content/ICCV2023/papers/Zeng_Parameterized_Cost_Volume_for_Stereo_Matching_ICCV_2023_paper.pdf 源码地址&#xff1a;https://github.com/jiaxiZeng/Parameterized-Cost-Volume-for-Stereo-Matching 概述 现有的立体匹…

c# 字段和属性(get、set、init)

目录 基本概念&#xff1a; 个人理解&#xff1a; 访问器的默认写法&#xff1a; set与init无法共存&#xff1a; init&#xff1a; 必须在类的实例化时给字段赋值的情况(require属性): 基本概念&#xff1a; “字段”就是类内成员变量&#xff0c;一般为了隐藏数据&…

超详细教程:使用React实现动态轮播图

前言 轮播组件是常见的一种方式&#xff0c;用来展示图像、信息或者是广告。我们可以使用React来创建一个轮播组件&#xff0c;并且利用其中的State和effect Hook来创建一款动态的、可以自动播放的轮播组件。 效果 轮播组件会展示一个平铺的图片列表。在图片列表下方是一组小…

java定位系统源码,UWB技术的无线定位系统源码

UWB技术是一种传输速率高&#xff0c;发射功率较低&#xff0c;穿透能力较强并且是基于极窄脉冲的无线技术。UWB最优的应用环境是室内或者相对密闭的空间&#xff0c;有着厘米级的定位精度&#xff0c;不仅可以非常精准地进行位置跟踪&#xff0c;还可以快速地进行数据传输。 智…

基于亚马逊云科技新功能:Amazon SageMaker Canvas无代码机器学习—以构建货物的交付状态检测模型实战为例深度剖析以突显其特性

授权说明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在亚马逊云科技开发者社区、 知乎、自媒体平台、第三方开发者媒体等亚马逊云科技官方渠道。 目录 &#x1f680;一. Amazon SageMaker &#x1f50e;1.1 新功能发布&#xff1a;A…

消息通知(Notification)/用户触达系统设计

近年来&#xff0c;通知功能已经成为许多应用程序中突出的特性。构建一个能每天发送数百万通知的可扩展系统绝非易事。这正是为什么我觉得有必要记录我在这方面踩坑之路。也叫用户触达系统。 完成这项任务要求对通知生态系统有深刻的理解&#xff0c;否则需求很容易变得模糊和…

系列十五、Redis面试题集锦

一、Redis面试题集锦 1.1、Redis到底是单线程还是多线程 Redis6.0版本之前的单线程指的是其网络IO和键值对读写是由一个线程完成的&#xff1b; Redis6.0引入的多线程指的是网络请求过程采用了多线程&#xff0c;而键值对读写命令仍然是单线程的&#xff0c;所以多线程环境下&…

MITO-ID®线粒体膜电位检测试剂盒

线粒体膜电位&#xff08;Mitochondrial Membrane Potential,MMP&#xff09;是判定细胞健康程度、线粒体膜通透性和细胞凋亡的一个重要指标&#xff0c;MMP的丧失通常与细胞凋亡的早期阶段有关。评估线粒体功能状态的基于细胞的检测方法正在成为阐明线粒体活动在药物诱导毒性、…

记录Oracle Exadata X8M-2 存储服务器告警灯亮的处理过程(/SYS/MB/P0PCIE7)

文章目录 概要调查流程处理方式&#xff1a; 概要 现场服务器告警灯亮&#xff0c;其他服务器正常&#xff0c;磁盘灯正常&#xff0c;所以从整体来看应是内部部件抛出的异常问题&#xff0c;需要登录机器确认&#xff1a; 调查流程 通过ILOM web界面查看服务器状态进行信息…

Java代码实现简易版王者荣耀

一.主窗口类 package com.sxt;import com.sxt.beast.Beast;import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.File; import java.util.Arra…

拓展 Amazon S3 技术边界:Amazon S3 Express One Zone 的创新之路

授权说明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 自 Amazon S3 服务推出以来&#xff0c;一直是全球各行各业数百万客…

Linux基本开发工具

编译器和自动化构建工具 一、编译器——gcc、g1. 安装 gcc/g2. 使用3. 链接库4. 拓展命令&#xff1a;od/file/ldd/readelf 二、自动化构建项目——make、makefile1. 介绍2. 使用例子touch——change file timestampsstat——display file or file system status修改时间 .PHON…

SpringBoot2—开发实用篇1

目录 热部署 手动启动热部署 自动启动热部署 热部署范围配置 关闭热部署 配置高级 测试 热部署 什么是热部署&#xff1f;简单说就是你程序改了&#xff0c;现在要重新启动服务器&#xff0c;嫌麻烦&#xff1f;不用重启&#xff0c;服务器会自己悄悄的把更新后的程序给…

大模型时代-怎么正确的开发和使用AI模型

一、背景 大模型的概念已经经过了一年的发酵&#xff0c;大家也渐渐的冷静下来了。一开始大家很兴奋&#xff0c;感觉新的时代要来了&#xff0c;然后就是疯狂&#xff0c;再就是都各自找各自公司的东西怎么与大模型沾点边&#xff0c;要不然今年玩不下去了&#xff0c;就要落伍…

数据结构和算法 - 数组

1、数组 1.1 简介 什么是数组&#xff1f; 他优缺点是什么&#xff1f;具体应用有哪些&#xff1f; 「数组 array」是一种基于顺序存储的线性数据结构&#xff0c;其将相同类型的元素存储在连续的内存空间中。我们将元素在数组中的位置称为该元素的「索引 index」。 如图&…

生产问题排查思路

生产上有用户反映&#xff0c;登录之后页面信息加载不出来&#xff0c;请求响应时间慢等。 下图为生产上一个请求在网关上面的流程&#xff1a; 因为我们生产上&#xff0c;有一张异常信息记录表&#xff0c;第一时间查询了上面是否有最近的异常记录。发现了一点&#xff0c;是…