11.偏向锁原理及其实战

news2024/11/18 23:32:18

文章目录

  • 偏向锁原理及其实战
    • 1.偏向锁原理
    • 2.偏向锁案例代码演示
      • 2.1.偏向锁案例代码
        • 2.2.1.无锁情况下状态
        • 2.1.2.偏向锁状态
        • 2.1.3.释放锁后的状态
      • 2.2.偏向锁的膨胀和撤销
        • 2.2.1.偏向锁撤销的条件
        • 2.2.2.偏向锁的撤销
      • 2.2.3.偏向锁的膨胀
    • 2.3.全局安全点原理和偏向锁撤销性能问题
      • 2.3.1.全局安全点介绍
      • 2.3.1 偏向锁撤销性能问题

偏向锁原理及其实战

偏向锁主要用来解决无竞争下锁性能问题,在实际场景中,如果一个同步代码块(方法)没有多个线程竞争,而且总是有一个线程多次重入获取锁,并且线程每次还有阻塞线程,更改线程状态为运行状态等操作,那么此时相对于CPU是一种资源浪费,为了解决这个问题,就引入了偏向锁

1.偏向锁原理

偏向锁原理:

  1. 当一个线程获取了一个对象的锁时,JVM会将该对象的锁标记位设置为01,偏向位设置为1,表示该对象进入了偏向锁状态。

  2. JVM会使用CAS(Compare and Swap)操作将获取锁的线程ID记录在对象的Mark Word中,如果CAS操作失败,说明有其他线程竞争获取锁。

  3. 当其他线程尝试获取该对象的锁时,JVM会检查对象的锁标记位和偏向位。如果锁标记位为01且偏向位为1,表示对象处于偏向锁状态,并且有线程ID记录在Mark Word中。

  4. 如果尝试获取偏向锁的线程ID与Mark Word中记录的线程ID相同,说明该线程仍然是获取锁的线程,可以直接进入同步代码块,无需使用CAS操作。

  5. 如果尝试获取偏向锁的线程ID与Mark Word中记录的线程ID不同,说明有其他线程竞争锁,此时偏向锁会自动升级为轻量级锁状态。

偏向锁的引入主要是为了优化无竞争情况下的锁性能。在无竞争的情况下,偏向锁可以避免多余的同步操作,从而提高程序的性能。然而,由于偏向锁需要记录线程ID并使用CAS操作,会引入一定的额外开销。因此,JVM会延迟启用偏向锁,只对一定时间后创建的对象进行偏向锁的开启。

虽然JVM默认开启偏向锁,但是延时 4s 开启,程序创建对象的时候并不会开启偏向锁, 4s后创建的对象才会开启偏向锁。

需要注意的是,偏向锁并不适用于具有竞争的情况,当存在多个线程竞争同一个对象的锁时,偏向锁会自动升级为轻量级锁或重量级锁,以保证线程的互斥访问和数据一致性。

在这里插入图片描述

2.偏向锁案例代码演示

2.1.偏向锁案例代码

注意 新版的JDK可能默认是禁用偏向锁的 ,所以需要再JVM启动参数上添加 开启偏向锁的相关代码

-XX:+UseBiasedLocking

在这里插入图片描述

/**
 * 偏向锁
 */
public class BiasedLockDemo {


    private static final Logger log = LoggerFactory.getLogger(BiasedLockDemo.class);

    @Test
    @DisplayName("偏向锁测试")
    public void test() {
        log.error("JVM详细信息: {}", VM.current().details());
        // 休眠5s
        SleepUtil.sleepMillis(5000);

        // 创建对象
        MyObjectLock myObjectLock = new MyObjectLock();
        log.error("无锁情况下,lock的状态!");
        myObjectLock.printLockStatus();

        SleepUtil.sleepMillis(5000);
        CountDownLatch latch = new CountDownLatch(1);
        Runnable runnable = ()->{
            // 模拟同一个线程多次进入同步代码块
            for (int i = 0; i < 1000; i++) {
                synchronized (myObjectLock){
                    myObjectLock.increase();
                    if (i == 1000 / 2){
                        log.error("占有锁情况下!lock状态!");
                        myObjectLock.printLockStatus();
                    }
                }
                SleepUtil.sleepMillis(10);
            }
            latch.countDown();
        };
        new Thread(runnable,"biased-thread").start();

        // 等待所有枷锁线程执行完毕
        try {
            latch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 等待5s 查看锁的状态
        SleepUtil.sleepMillis(5000);
        log.error("释放锁后锁的状态!");
        myObjectLock.printLockStatus();

    }
}
class MyObjectLock{
    private static final Logger log = LoggerFactory.getLogger(MyObjectLock.class);


    private int count = 0;

    /**
     * 打印当前对象的一个状态
     */
    public void printLockStatus(){
        log.error(ClassLayout.parseInstance(this).toPrintable());
    }

    /**
     * 将当前共享变量自增
     */
    public void increase(){
        this.count++;
    }
}

这里 我们分为三个部分进行说明

2.2.1.无锁情况下状态

在这里插入图片描述

  • 对象头部分的第一个4字节(OFFSET 0)表示对象的标记字(Mark Word)。在这个结果中,标记字的十六进制表示为05 00 00 00,对应的二进制为00000101 00000000 00000000 00000000。其中,第一个位为偏向锁标志位,为1表示启用了偏向锁。在这个结果中,偏向锁标志位为1,说明该对象启用了偏向锁。
  • 其中 d8 f9 09 01 (11011000 11111001 00001001 00000001) (17431000)为其Class Pointer(类对象指针)
  • 对象头部分的第二个4字节(OFFSET 4)和第三个4字节(OFFSET 8)也是对象头信息。在这个结果中,这两个字节的值都为0,没有包含其他重要的标志位信息。
  • 接下来的4字节(OFFSET 12)表示MyObjectLock对象中的一个整型字段count。在这个结果中,count字段的值为0。
  • 最后,结果显示了对象的实例大小为16字节,并且没有内部或外部的空间损失。
2.1.2.偏向锁状态

在输出MyObjectLock实例结构后,等待5s,然后启动一个线程占用偏向锁,因为输出的内容比较多,所以这里选择了到中间值的时候进行输出结构。

偏向锁状态

  • 对象头部分的第一个4字节(OFFSET 0)表示对象的标记字(Mark Word)。在这个结果中,标记字的十六进制表示为05 80 e6 c1,对应的二进制为00000101 10000000 11100110 11000001。其中,第一个位为偏向锁标志位,为1表示启用了偏向锁。在这个结果中,偏向锁标志位为1,说明该对象启用了偏向锁。
  • 对象头部分的第二个4字节(OFFSET 4)和第三个4字节(OFFSET 8)也是对象头信息。在这个结果中,这两个字节的值分别为c0 02 00 00d8 f9 09 01
  • 接下来的4字节(OFFSET 12)表示MyObjectLock对象中的一个整型字段count。在这个结果中,count字段的值为501。
  • 最后,结果显示了对象的实例大小为16字节,并且没有内部或外部的空间损失。
2.1.3.释放锁后的状态

在这里插入图片描述

  • 对象头部分的第一个4字节(OFFSET 0)表示对象的标记字(Mark Word)。在这个结果中,标记字的十六进制表示为05 80 e6 c1,对应的二进制为00000101 10000000 11100110 11000001。其中,第一个位为偏向锁标志位,为1表示启用了偏向锁。在这个结果中,偏向锁标志位为1,说明该对象启用了偏向锁。
  • 对象头部分的第二个4字节(OFFSET 4)和第三个4字节(OFFSET 8)也是对象头信息。在这个结果中,这两个字节的值分别为c0 02 00 00d8 f9 09 01
  • 接下来的4字节(OFFSET 12)表示MyObjectLock对象中的一个整型字段count。在这个结果中,count字段的值为1000。
  • 最后,结果显示了对象的实例大小为16字节,并且没有内部或外部的空间损失。

2.2.偏向锁的膨胀和撤销

假如有多个线程来竞争偏向锁,此对象锁就不会有所偏向了,其他线程发现偏向锁并不是偏向自己,就说明存在了竞争,会尝试撤销偏向锁,然后膨胀到轻量级锁。

2.2.1.偏向锁撤销的条件
  • 多个线程存在竞争
  • 调用偏向锁对象的obj的obj.hashCode或者System.indentityHsahCode()方法计算对象的hash码,偏向锁将会被撤销。
    • 因为一个对象的哈希码只会生成一次,并且保存在Mark Word中,偏向锁的Mark Word 已经保存了线程ID,没有其他地方在保存哈希码了,所以只能撤销偏向锁
    • 轻量级锁会在栈帧的Lock Record(锁记录)中记录哈希码
    • 重量级锁会在监视器中记录哈希码
2.2.2.偏向锁的撤销

偏向锁的撤销的开销花费是挺大的,其大概过程如下

  1. JVM需要等待一个全局安全点,当JVM到达全局安全点后,所有的用户线程都是暂停的,当前持有偏向锁的用户线程也是暂停的。
  2. 遍历线程的栈帧,检查是否存在锁记录,如果存在锁记录,那么就清空锁记录,使其变成无锁的状态,并修复锁记录指向的线程ID,清除其线程ID
  3. 将当前锁升级(或碰撞)成轻量级锁,少数场景直接升级为重量级锁
  4. 唤醒当前线程

2.2.3.偏向锁的膨胀

如果偏向锁被占据,那么第二个线程争抢这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到内置锁的偏向状态,这时JVM会检查原来持有该偏向锁线程是否存活,如果挂了,那么将该对象变为无锁状态,重新偏向,如果没挂,就发生竞争,进行膨胀。

  1. 当一个线程尝试获取一个偏向锁时,如果该锁的标记字(Mark Word)指向的线程ID与当前线程ID不一致,表示存在竞争,此时偏向锁需要膨胀。
  2. JVM会将对象的标记字从偏向锁状态修改为轻量级锁状态。这个过程称为锁的膨胀。
  3. 膨胀为轻量级锁的过程包括以下步骤:
    • JVM会尝试使用CAS(Compare and Swap)操作将对象的标记字修改为指向锁记录的指针,同时更新锁记录中的线程ID为当前线程ID。
    • 如果CAS操作成功,表示膨胀为轻量级锁成功。
    • 如果CAS操作失败,说明存在竞争,此时会进一步膨胀为重量级锁。
  4. 膨胀为重量级锁的过程包括以下步骤:
    • JVM会在堆中分配一个监视器(Monitor)对象,用于管理锁的状态。
    • 将对象的标记字指向该监视器对象,并将锁记录中的线程ID修改为0。
    • 此时,锁的状态变为重量级锁。

2.3.全局安全点原理和偏向锁撤销性能问题

2.3.1.全局安全点介绍

在Java虚拟机(JVM)中,全局安全点(Global Safepoint)是一个重要的概念,用于确保在某个特定的时间点,所有的线程都处于安全状态,可以被安全地中断。全局安全点的引入是为了支持一些关键操作,例如线程停止、垃圾回收等。

实现全局安全点的核心思想是在代码的特定位置插入安全点检查。安全点检查是一段特殊的机器码指令,用于判断当前线程是否到达安全点。当一个线程到达安全点时,它会被停止,并且进一步的操作需要等待其他线程也到达安全点。只有当所有线程都到达安全点时,JVM才能执行需要线程处于安全状态的操作。

全局安全点的引入是为了解决并发线程访问共享数据的一致性问题。在偏向锁撤销的过程中,JVM需要等待全局安全点的到来,以确保所有的线程都被暂停,包括持有偏向锁的线程。只有当所有线程都暂停时,JVM才能安全地撤销偏向锁,并进行相应的操作。

全局安全点的实现机制主要包括以下几个方面:

  1. 安全点的选定:
    JVM会选择一些合适的位置作为安全点,通常是在方法调用、循环跳转等代码块的末尾。这些位置被认为是安全点,因为在这些位置上线程处于安全状态,可以被安全地中断。安全点的选定通常是通过静态分析和动态检测相结合的方式来确定的。
  2. 安全点的设置:
    JVM会在代码中插入安全点检查的指令,用于检查当前线程是否到达安全点。这些指令通常是无操作(NOP)指令或轻量级的计算指令,不会对程序的语义产生影响。当线程执行到安全点检查指令时,会检查当前线程是否到达安全点,如果没有到达则会等待其他线程也到达安全点。
  3. 安全点的等待:
    当一个线程到达安全点时,它会被停止,并且进一步的操作需要等待其他线程也到达安全点。只有当所有线程都到达安全点时,JVM才能执行需要线程处于安全状态的操作。线程的等待是通过自旋等待或挂起等待的方式来实现的。
  4. 安全点的恢复:
    当所有线程都到达安全点后,JVM可以执行需要线程处于安全状态的操作。完成操作后,JVM会恢复线程的执行,使其继续执行下去。恢复线程的执行可以通过唤醒等待线程或者设置标记等方式来实现。

2.3.1 偏向锁撤销性能问题

偏向锁撤销的过程相对于偏向锁的获取和释放而言,具有较高的开销。这是因为偏向锁撤销需要等待全局安全点,并进行一系列的操作来撤销偏向锁。这些额外的操作会增加系统开销,影响性能。

偏向锁撤销的性能问题主要体现在以下几个方面:

  1. 全局安全点的等待时间:在偏向锁撤销过程中,JVM需要等待全局安全点的到来。这会导致线程在等待期间无法执行其他有用的工作,从而造成性能损失。
  2. 线程栈帧的遍历和修改:在偏向锁撤销过程中,JVM需要遍历线程的栈帧,检查是否存在锁记录,并对锁记录进行修改。这涉及到线程状态的切换和寻址操作,会增加额外的开销。
  3. 锁的状态转换:偏向锁撤销后,锁需要进行状态转换,通常是升级为轻量级锁或重量级锁。这种状态转换涉及到锁的标记字和锁记录的修改,可能需要进行CAS(Compare and Swap)操作,增加了额外的开销。

为了减少偏向锁撤销的性能开销,可以采取一些优化措施,例如调整全局安全点的触发频率、优化线程栈帧的遍历算法,以及采用延迟偏向锁撤销等策略。这些优化措施可以提高偏向锁的性能并减少性能损失。然而,需要注意的是,在特定的应用场景下,偏向锁的撤销可能仍然会带来一定的性能开销。因此,在设计和使用偏向锁时需要综合考虑性能与应用需求之间的平衡。

对于高并发应用来说,一般建议关闭偏向锁(JDK9之后,偏向锁默认是关闭的)

-XX:-UseBiasedLocking 

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

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

相关文章

在R的 RGui中,使用devtools 安装trajeR

创建于&#xff1a;2024.5.5 文章目录 1. 报错信息2. 尝试使用指定的清华镜像&#xff0c;没有解决3. 找到原因&#xff1a;官网把包删除了4. 尝试从网上下载&#xff0c;然后安装。没有成功5. 使用devtools安装5.1 尝试直接安装&#xff1a;install.packages("devtools&q…

OpenCV | 项目 | 虚拟绘画

OpenCV | 项目 | 虚拟绘画 捕捉摄像头 如果在虚拟机中运行&#xff0c;请确保虚拟机摄像头打开。 #include<opencv2/opencv.hpp>using namespace cv; using namespace std;int main() {VideoCapture cap(0);Mat img;while(1) {cap.read(img);imshow("Image"…

JetBrains的Java集成开发环境IntelliJ 2024.1版本在Windows/Linux系统的下载与安装配置

目录 前言一、IntelliJ在Windows安装二、IntelliJ在Linux安装三、Windows下使用配置四、Linux下使用配置总结 前言 ​ “ IntelliJ IDEA Ultimate是一款功能强大的Java集成开发环境&#xff08;IDE&#xff09;。它提供了丰富的功能和工具&#xff0c;可以帮助开发人员更高效地…

labview技术交流-将时间字符串转换成时间格式

应用场景 我们在数据库中设计了datetime类型的字段&#xff0c;比如字段名就叫“保存时间”&#xff0c;当我们使用labview将表中数据读取出来后datetime类型的数据是以字符串的格式显示的。而我们想计算两条数据“保存时间”的间隔时间时&#xff0c;用字符串类型自然是没法计…

uniapp读取项目本地文件/json文件/txt文件

uniapp读取项目本地文件/json文件/txt文件 文件必须放在static目录下 方法&#xff1a; /*** 访问static里面的文件* param url 文件路径 必须在static目录下*/ function localFetch(url) {return new Promise((resolve, reject) > {plus.io.resolveLocalFileSystemURL(_ww…

OmniReader Pro mac激活版:智慧阅读新选择,开启高效学习之旅

在追求知识的道路上&#xff0c;一款优秀的阅读工具是不可或缺的。OmniReader Pro作为智慧阅读的新选择&#xff0c;以其独特的功能和卓越的性能&#xff0c;为您开启高效学习之旅。 OmniReader Pro具备高效的文本识别和处理技术&#xff0c;能够快速准确地提取文档中的关键信息…

PXE批量部署,一键安装配置多台Linux系统

目录 一、PXE批量部署的优点 二、搭建PXE远程安装服务器 1. 实验初始化设置 2. 一键安装软件包 3. 复制 vmlinuz、initrd.img、pxelinux.0文件 4. 配置PE启动菜单配置文件 5. 修改配置文件&#xff0c; 启动各个软件服务 6. kickstart自动应答文件修改启动菜单配置文件…

(一)Linux的vim编辑器的使用

一.vim编辑器 Vim 是从 vi 发展出来的一个文本编辑器。代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。简单的来说, vi 是老式的字处理器,不过功能已经很齐全了,但是还是有可以进步的地方。 vim 则可以说是程序开发者的一项很好用的工具。 二…

不抽象:Increase API 设计原则

原文&#xff1a;Increase - 2024.04.26 &#xff08;注&#xff1a;Increase 是一家提供金融技术服务的公司。&#xff09; API 资源是 API 的实体或对象。决定如何为这些实体命名和建模可以说是设计 API 最难也是最重要的部分。您所公开的资源组织了用户对您的产品如何工作…

什么才是正确的领域驱动实现架构?

作为一种系统建模方法&#xff0c;DDD同样涉及系统的体系架构设计。区别于分布式、事件驱动、消息总线等架构设计方法&#xff0c;DDD中的架构设计关注前面各章所介绍的聚合、实体、值对象、领域事件、应用服务以及资源库之间的交互方式和风格&#xff0c;并在设计思想上有其独…

揭秘设计师必备神器:情绪板是什么?

每个伟大的设计项目都从一点灵感开始。无论你是在设计网站、应用程序&#xff0c;还是想重新装修房子&#xff0c;情绪板都可以帮助你激发创造力&#xff0c;甚至情绪板也可以决定UI界面是否成功。本文将分享什么是情绪板&#xff0c;为什么需要情绪板&#xff0c;以及如何充分…

Linux下多线程相关概念

thread 1.什么是线程1.1 线程优缺点1.2 线程异常1.3 线程用途 2. 进程和线程区别3. 线程控制3.1 POSIX线程库3.2 pthread_create()3.3 线程ID3.4 线程ID地址空间布局pthread_self() 3.5 线程终止pthread_exit函数pthread_cancle函数 3.6 线程等待3.7 分离线程__thread修饰全局变…

OpenCV Radon变换探测直线(拉东变换)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 Radon变换可以将原始图像中直线特征的处理问题转化为变换域图像中对应点特征的处理问题,其中对应特征点的横坐标表示原始图像的旋转角度,一般来讲原始图像中的噪声不会分布在直线的特征上。因此,Radon变换在探测…

Python实战开发及案例分析(12)—— 模拟退火算法

模拟退火算法&#xff08;Simulated Annealing&#xff09;是一种概率搜索算法&#xff0c;源自于金属退火过程。在金属退火中&#xff0c;通过缓慢降低温度&#xff0c;金属内部的原子能够从高能态逐步达到较低能态。模拟退火算法利用类似的原理&#xff0c;通过随机搜索和概率…

吉时利2400与Keithley 2450 SMU 数字源表区别?

Keithley SMU&#xff08;源测量单元&#xff09;数字源表是一种精密的电子测试设备&#xff0c;它结合了电流和电压源以及测量功能。这些设备被设计用于需要紧密耦合源和测量的测试应用中。Keithley 2400系列SMU数字源表提供了四象限精密电压和电流源/负载&#xff0c;以及触摸…

远程智控BACnet/IP I/O模块助力Metasys系统无缝对接

江森自控的Metasys系统以其强大的综合管理能力成为众多楼宇自控项目的首选平台。然而&#xff0c;面对日益增长的个性化需求与复杂多变的设备接入挑战&#xff0c;如何高效、灵活地扩展其I/O控制能力成为关键。在此背景下&#xff0c;BACnet/IP分布式远程I/O模块的出现&#xf…

可视化大屏的应用:电子政务领域的巨大应用价值

可视化大屏在电子政务领域的应用价值主要体现在以下几个方面&#xff1a; 数据监控与分析 可视化大屏可以将政务数据以图表、地图等形式展示在大屏上&#xff0c;帮助政府部门实时监控和分析各项指标和数据变化。例如&#xff0c;可以实时显示人口统计、经济指标、环境监测等…

如何评估大模型音频理解能力-从Gemini说起

Gemini家族包含Ultra、Pro和Nano三种大小的模型是谷歌开发的大型多模态人工智能模型&#xff0c;它在人工智能的多模态领域实现了重大突破&#xff0c;结合了语言、图像、音频和视频的理解能力。 Gemini的性能评估情况如下&#xff1a; Gemini模型的评估的具体指标从文本理解能…

专题六_模拟(1)

目录 1576. 替换所有的问号 解析 题解 495. 提莫攻击 解析 题解 1576. 替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; 解析 题解 class Solution { public:string modifyString(string s) {// 40.专题六_模拟_替换所有的问号_Cint n s.…

Qt跨平台开发demo(适用萌新)

最近需要参与一款Qt跨平台的软件开发&#xff0c;在此之前&#xff0c;特把基础信息做学习和梳理&#xff0c;仅供参考。 所使用的技术和版本情况如下&#xff1a; 虚拟机&#xff1a;VMware 16.2.5操作系统&#xff1a;ubuntu-20.04.6-desktop-amd64&#xff1a;Mysql数据库…