11.偏向锁原理及其实战

文章目录

  • 偏向锁原理及其实战
    • 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());// 休眠5sSleepUtil.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.mzph.cn/web/10740.shtml

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

相关文章

EPAI手绘建模APP工程图顶部工具栏

7、工程图 图 302 工程图 工程图包括顶部常用工具栏、右侧工程图工具栏、左侧模型列表栏、中间的工程图。 (1) 常用工具栏 ① 删除&#xff0c;选中场景中工程图元素后&#xff0c;删除。可以选择多个工程图元素同时删除。 ② 设置&#xff0c;打开工程图设置页面&#xff0…

2024 年最新本地、云服务器安装部署 miniconda 环境详细教程(更新中)

Anaconda 概述 Anaconda 是专门为了方便使用 Python 进行数据科学研究而建立的一组软件包&#xff0c;涵盖了数据科学领域常见的 Python 库&#xff0c;并且自带了专门用来解决软件环境依赖问题的 conda 包管理系统。主要是提供了包管理与环境管理的功能&#xff0c;可以很方便…

picoCTF-Web Exploitation-More SQLi

Description Can you find the flag on this website. Additional details will be available after launching your challenge instance. Hints SQLiLite 先随便输入个账号密码登录一下&#xff0c;得到查询SQL&#xff0c;接下来应该对SQL进行某些攻击来绕过密码登录成功 -- …

微信小程序踩坑,skyline模式下,简易双向绑定无效

工具版本 基础库版本 Skline模式 页面json设置 问题描述 skyline模式下,textarea,input标签设置简易双向绑定 model:value是无效的,关闭skyline模式就正常使用了 截图展示 这里只展示了textarea标签,input标签的简易双向绑定也是无效的 总结 我在文档里面是没找到skyline里面不…

动态规划----股票买卖问题(详解)

目录 一.买卖股票的最佳时机&#xff1a; 二.买卖股票的最佳时机含冷冻期&#xff1a; 三.买卖股票的最佳时期含⼿续费&#xff1a; 四.买卖股票的最佳时机III: 五.买卖股票的最佳时机IV: 买卖股票的最佳时机问题介绍&#xff1a;动态规划买卖股票的最佳时机是一个经典的…

windows使用Docker-Desktop部署lobe-chat

文章目录 window安装docker-desktop下载和启动lobe-chatAI大语言模型的选择lobe-chat设置大模型连接 window安装docker-desktop docker-desktop下载地址 正常安装应用&#xff0c;然后启动应用&#xff0c;注意启动docker引擎 打开右上角的设置&#xff0c;进入Docker Engine设…

算法学习系列(六十):区间DP

目录 引言区间合并模板一、石子合并二、环形石子合并三、能量项链 引言 关于这个区间 D P DP DP &#xff0c;其实是有套路和模板的&#xff0c;题型的话也是变化不多&#xff0c;感觉就那几种&#xff0c;只不过有些题会用到高精度或者是要记录方案&#xff0c;所以整体来说…

Unity编辑器如何多开同一个项目?

在联网游戏的开发过程中&#xff0c;多开客户端进行联调是再常见不过的需求。但是Unity并不支持编辑器多开同一个项目&#xff0c;每次都得项目打个包(耗时2分钟以上)&#xff0c;然后编辑器开一个进程&#xff0c;exe 再开一个&#xff0c;真的有够XX的。o(╥﹏╥)o没错&#…

Rust学习笔记(上)

前言 笔记的内容主要参考与《Rust 程序设计语言》&#xff0c;一些也参考了《通过例子学 Rust》和《Rust语言圣经》。 Rust学习笔记分为上中下&#xff0c;其它两个地址在Rust学习笔记&#xff08;中&#xff09;和Rust学习笔记&#xff08;下&#xff09;。 编译与运行 Ru…

python使用yaml文件以及元组样式字符串使用eval的类型转换

编程中&#xff0c;对于可变内容&#xff0c;最好是将其放入配置文件中&#xff0c;经过这段时间的学习&#xff0c;感觉使用yaml文件很方便。我的环境&#xff1a;win10&#xff0c;python3.8.10。 python使用yaml文件&#xff0c;首先要安装库。 pip38 install pyyaml 安装…

AWTK 开源串口屏开发(18) - 用 C 语言自定义命令

AWTK-HMI 内置了不少模型&#xff0c;利用这些模型开发应用程序&#xff0c;不需要编写代码即可实现常见的应用。但是&#xff0c;有时候我们需要自定义一些命令&#xff0c;以实现一些特殊的功能。 本文档介绍如何使用 C 语言自定义命令。 1. 实现 hmi_model_cmd_t 接口 1.1…

实现二叉树的基本操作

博主主页: 码农派大星. 关注博主带你了解更多数据结构知识 1我们先来模拟创建一个二叉树 public class TestBinaryTreee {static class TreeNode{public char val;public TreeNode left;public TreeNode right;public TreeNode(char val) {this.val val;}}public TreeNode …

linux 安装 mangodb 并设置服务开机自启

1、下载 wget http://mosquitto.org/files/source/mosquitto-1.6.8.tar.gz 2、解压 tar -zxvf mosquitto-1.6.8.tar.gz 3、编译安装cd mosquitto-1.6.8 make sudo make install4、在当前目录。进入mosquitto服务文件存放的文件夹 cd service/systemd可以看到3个文件 点击read…

【C/C++】设计模式——工厂模式:简单工厂、工厂方法、抽象工厂

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

二.基础篇: 面向对象进阶

1. 基础篇语法篇&#xff1a;一.基础篇&#xff1a;基础语法-CSDN博客 面向对象进阶 本章主要学习内容&#xff1a; static继承包&#xff0c;final&#xff0c;权限修饰符&#xff0c;代码块抽象类接口多态内部类 1. static static翻译过来就是静态的意思static表示静态&am…

AI语音模型PaddleSpeech踩坑(安装)指南

PaddleSpeech简介 PaddleSpeech 是基于飞桨 PaddlePaddle 的语音方向的开源模型库&#xff0c;用于语音和音频中的各种关键任务的开发&#xff0c;包含大量基于深度学习前沿和有影响力的模型。 PaddleSpeech安装步骤 提示&#xff1a;要找到一个合适的PaddleSpeech版本与pad…

java项目之相亲网站的设计与实现源码(springboot+mysql+vue)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的相亲网站的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 相亲网站的设计与实…

连升三级!openGauss单机版从2.1.0经停3.0.0升级至5.0.0

前言 如前文所述&#xff0c;我们的小demo项目起初安装了openGauss的2.1.0版本&#xff0c;由于2.1.0不是长期维护&#xff08;LTS&#xff09;版本&#xff0c;所以要升级到5.0.0LTS。考虑到虽然是DEMO项目&#xff0c;但也有些体验用户&#xff0c;所以为了保障业务连续性&a…

2023版brupsuite专业破解安装

安装教程&#xff0c;分两部分&#xff1a; 1、安装java环境、参考链接JAVA安装配置----最详细的教程&#xff08;测试木头人&#xff09;_java安装教程详细-CSDN博客 2、安装2023.4版本brupsuite&#xff1a;参考链接 2023最新版—Brup_Suite安装配置----最详细的教程&…

Java---类和对象第一节

目录 1.面向对象初步认识 1.1什么是面向对象 1.2面向对象和面向过程的区别 2.类的定义和使用 2.1简单认识类 2.2类的定义格式 2.3类的实例化 2.4类和对象的说明 3.this关键字 3.1访问本类成员变量 3.2调用构造方法初始化成员变量 3.3this引用的特性 4.对象的构造以…