Synchronizad优化原理(JUC)

目录

  • java对象头
    • 一:Monitor
    • 二:sychronized的优化
      • 轻量级锁
      • (轻量级锁)锁膨胀(重量级锁)
      • (重量级锁)锁自旋
      • 偏向锁(比轻量级锁更轻量)
        • 偏向锁状态
        • 如何撤销偏向锁
        • 批量重偏向
        • 批量撤销
      • 锁擦除

java对象头

一:Monitor

  • Monitor翻译过来可以叫监视器和管程。
  • 当给某个对象加上锁之后,这个对象的对象头中的MarkWord就会指向一个Monitor。

结构如下

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 刚开始OWner为空,当有线程获得锁时,owner就会指向该线程;

  • 当有其他线程执行到sychronized时,因为一个owner只能指向一个线程,所以它就会进入EntryList进入BLOCKED状态

  • 当线程执行完释放锁时,会通知Monitor,Monitor会唤醒EntryList中的线程,然后这些线程会进行非公平竞争来获取锁;

    注意:

  • 一个对象只有一个Monitor

二:sychronized的优化

  • 故事

  • 故事角色

    • 老王 - JVM
    • 小南 - 线程
    • 小女 - 线程
    • 房间 - 对象
    • 房间门上 - 防盗锁 - Monitor
    • 房间门上 - 小南书包 - 轻量级锁
    • 房间门上 - 刻上小南大名 - 偏向锁
    • 批量重刻名 - 一个类的偏向锁撤销到达 20 阈值
    • 不能刻名字 - 批量撤销该类对象的偏向锁,设置该类不可偏向

    小南要使用房间保证计算不被其它人干扰(原子性),最初,他用的是防盗锁,当上下文切换时,锁住门。这样即使他离开了,别人也进不了门,他的工作就是安全的。

    但是,很多情况下没人跟他来竞争房间的使用权。小女是要用房间,但使用的时间上是错开的,小南白天用,小女晚上用。每次上锁太麻烦了,有没有更简单的办法呢?

    小南和小女商量了一下,约定不锁门了,而是谁用房间,谁把自己的书包挂在门口,但他们的书包样式都一样,因此每次进门前得翻翻书包,看课本是谁的,如果是自己的,那么就可以进门,这样省的上锁解锁了。万一书包不是自己的,那么就在门外等,并通知对方下次用锁门的方式。

    后来,小女回老家了,很长一段时间都不会用这个房间。小南每次还是挂书包,翻书包,虽然比锁门省事了,但仍然觉得麻烦。

    于是,小南干脆在门上刻上了自己的名字:【小南专属房间,其它人勿用】,下次来用房间时,只要名字还在,那么说明没人打扰,还是可以安全地使用房间。如果这期间有其它人要用这个房间,那么由使用者将小南刻的名字擦掉,升级为挂书包的方式。

    同学们都放假回老家了,小南就膨胀了,在 20 个房间刻上了自己的名字,想进哪个进哪个。后来他自己放假回老家了,这时小女回来了(她也要用这些房间),结果就是得一个个地擦掉小南刻的名字,升级为挂书包的方式。老王觉得这成本有点高,提出了一种批量重刻名的方法,他让小女不用挂书包了,可以直接在门上刻上自己的名字

    后来,刻名的现象越来越频繁,老王受不了了:算了,这些房间都不能刻名了,只能挂书包

轻量级锁

  • 当一个对象有多线程要加锁,但是多线程加锁的时间是错开的,这个时候我们可以使用轻量级锁来优化
  • 轻量级锁是透明的,即我们还是使用sychronized方法进行加锁;

我们研究一下一段代码:

static final Object obj = new Object();

public static void method1() {
    synchronized( obj ) {
        // 同步块 A
        method2();
    }
}

public static void method2() {
    synchronized( obj ) {
        // 同步块 B
    }
}
  • 这里加锁的流程:
  • 刚开始加锁会在线程的栈帧中加入一个锁记录对象,然后将锁记录中的对象引用指向加锁对象的地址;

  • 然后尝试用cas替换对象头中的MarkWord,将MarkWord存入锁记录;

  • 如果cas成功,对象头中就保存了锁地址和状态00;表示该线程给对象加锁
  • cas失败有两种情况:
  • 1:如果其他线程已经持有了Object轻量级锁,说明具有锁竞争,进入锁膨胀状态;
  • 2:如果是synchronizad锁重入,那么就会在加一条lock record记录重入的次数,这里的锁记录为null;
  • 然后继续指向,再次对这个对象加锁,就会形成synchronized锁重入现象,创建一个lock recode记录重入次数:

  • 当解锁时,如果lock record的锁记录为空那么就删除该锁记录,同时重入次数-1;
  • 如果解锁是,lockrecord的锁记录不为空,那么就会使用cas将MarkWord恢复给对象
    • 成功
    • 失败:说明进行了所膨胀或者是升级为重量级锁,进入重量级锁解锁流程;

(轻量级锁)锁膨胀(重量级锁)

  • 在cas失败,也就是已经有线程持有object锁对象了,那么就会进入锁膨胀,将轻量级锁转变为重量级锁。
  • 首先Object会申请Monitor,然后object会指向Monitor的地址,然后将线程加入到EntryList进入BLOCKED状态进行等待;
  • 然后原本持有轻量级锁的线程使用cas将MarkWord还给对象时会失败,然后就会根据Monitor的地址找到Monitor,将Owner置为空,然后唤醒EntryListBLOCKED的线程;

(重量级锁)锁自旋

在重量级锁竞争过程中,会通过自旋(循环获取重量级锁)来进行优化,如果获取锁成功(持锁线程释放了锁),就可以避免进入阻塞状态(从阻塞再恢复会进行上下文切换,比较耗费性能)

注意:

  • 自旋回占用cpu资源,单核进行自旋没有意义,多核才有效果
  • java6 之后的锁自旋是很智能的;
  • java7之后可以开启或者关闭锁自旋;

偏向锁(比轻量级锁更轻量)

  • 我们在使用轻量级锁在没有竞争时进行锁重入的时候,还是会进行cas操作;
  • 我们可以使用偏向锁,偏向锁只会在第一次获取锁时使用cas将线程的ID设置为对象头中的MarkWord,之后在没有竞争的情况下,发现线程id是线程本身的话就不会进行cas操作,对象属于该线程;
偏向锁状态

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 一个对象在创建时:

    • 默认是可以使用偏向锁,他的MarkWord后三位默认是101,其他位都是0;
    • 但是偏向锁开启默认都是延时的,想要避免延时可以加上Vm参数:-XX:BiasedLockingStartupDelay=0禁用延迟
    • 如果禁用了偏向锁,那么创建对象之后的MarkWord后三位是001;

    我们可以来测试偏向锁:


public static void main(String[] args) throws IOException {
    Dog d = new Dog();
    ClassLayout classLayout = ClassLayout.parseInstance(d);
    
    new Thread(() -> {
        log.debug("synchronized 前");
        System.out.println(classLayout.toPrintableSimple(true));
        synchronized (d) {
            log.debug("synchronized 中");
            System.out.println(classLayout.toPrintableSimple(true));
        }
        log.debug("synchronized 后");
        System.out.println(classLayout.toPrintableSimple(true));
    }, "t1").start();
}

输出:

11:08:58.117 c.TestBiased [t1] - synchronized00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
11:08:58.121 c.TestBiased [t1] - synchronized00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101 
11:08:58.121 c.TestBiased [t1] - synchronized00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101

这里关闭了延时所以刚开始就是101,然后加上偏向锁之后在Markword中就多了线程ID,之后就是释放了锁也是有线程ID,这个就是偏向,加锁之后就属于该线程了;

如果禁用偏向锁:

11:13:10.018 c.TestBiased [t1] - synchronized00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
11:13:10.021 c.TestBiased [t1] - synchronized00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000 
11:13:10.021 c.TestBiased [t1] - synchronized00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

加锁之后默认使用的是轻量级锁,是00;

在调用hashcode之后会车撤销偏向锁,因为对象头中没有位置;

我们知道锁的优先级是偏向锁,轻量级锁,重量级锁;

如何撤销偏向锁
  • 使用hashcode
  • 其他线程来获取锁(不是同时来竞争。出现竞争就会转变成重量级锁了)
  • 使用wait/notify
批量重偏向
  • 当对象被多线程访问,但是没有竞争时,此时偏向于t1线程的偏向锁有机会偏向t2线程,会重置线程ID;
  • 当撤销偏向锁的阈值超过20次时,jvm会认为偏向锁偏向错了,会在加锁时重新偏向于新线程;
批量撤销
  • 当撤销锁的阈值超过40次时,Jvm就会判断,是不是不应该偏向,于是这个类的所有对象都变成了不可偏向,新建的对象也都是不可偏向;

锁擦除


@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations=3)
@Measurement(iterations=5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
    static int x = 0;
    @Benchmark
    public void a() throws Exception {
        x++;
    }
    @Benchmark
    public void b() throws Exception {
        //这里的o是局部变量,不会被共享,JIT做热点代码优化时会做锁消除
        Object o = new Object();
        synchronized (o) {
            x++;
        }
    }
}
  • 这里要判断加锁和不加锁的性能差距,最后得出的结果是差不多的,为什么呢
  • 因为JIT即时编译器会对热点代码进行优化,这里他就会判断o变量是局部变量,没有逃出方法的作用范围不会被共享。是线程安全的所以会对其进行优化擦去锁;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android显示系统(08)- OpenGL ES - 图片拉伸

Android显示系统(02)- OpenGL ES - 概述 Android显示系统(03)- OpenGL ES - GLSurfaceView的使用 Android显示系统(04)- OpenGL ES - Shader绘制三角形 Android显示系统(05)- OpenGL…

Vscode 构建 uniapp vue3 + ts 微信小程序项目

前言 为什么要使用 Vscode 来开发构建 uniapp 项目?从个人角度来讲,仅是想要 Vscode 丰富的插件生态,以及最重要的优秀的 TtypeScript 类型检查支持,因为本人是 TS 重度使用者。 如果你更习惯使用 js 进行开发,使用 …

[游戏开发] Unity中使用FlatBuffer

什么是FlatBuffer 为什么用FloatBuffer,优势在哪? 下图是常规使用的各种数据存储类型的性能对比。 对序列化数据的访问不需要打包和拆包——它将序列化数据存储在缓存中,这些数据既可以存储在文件中,又可以通过网络原样传输&…

软件工程 概述

软件 不仅仅是一个程序代码。程序是一个可执行的代码,它提供了一些计算的目的。 软件被认为是集合可执行的程序代码,相关库和文档的软件。当满足一个特定的要求,就被称为软件产品。 工程 是所有有关开发的产品,使用良好定义的&…

负载均衡策略:L(P)策略;L(Max) ;L(LDS)

负载均衡策略:L(P)策略;L(Max) ;L(LDS) 1. Proportion load distribution L(P)策略; 策略含义:服务器不配置为可变服务率,调度器按照服务器服务率的倒数比例分配负载。即每个服务器分配到的任务量与该服务器服务率的倒数成正比 2. (L(Max)) load distribution((L…

探店小程序:解锁商业新生态,定制未来

在数字化浪潮席卷全球的今天,商业的边界正在被重新定义。随着移动互联网技术的飞速发展,探店小程序作为一种新兴的商业模式,正以其独特的优势迅速成为连接商家与消费者的桥梁。我们刚刚为一家客户成功交付了一款集分销、分润、商业模式定制开…

从EXCEL表格到WEB TABLE的实践

前言 EXCEL管理数据 Bootstrap Bootstrap 是一个流行的开源前端框架,它由 Twitter 的员工开发,用于快速开发响应式和移动设备优先的网页和应用程序。 jQuery jQuery 是一个快速、小巧且功能丰富的 JavaScript 库。它简化了 HTML 文档的遍历、事件处理…

HarmonyOS(65) ArkUI FrameNode详解

Node 1、Node简介2、FrameNode2.1、创建和删除节点2.2、对FrameNode的增删改2.3、 FramNode的查询功能3、demo源码4、总结5、参考资料1、Node简介 在HarmonyOS(63) ArkUI 自定义占位组件NodeContainer介绍了自定义节点复用的原理(阅读本本篇博文之前,建议先读读这个),在No…

独家首发 | 基于 KAN、KAN卷积的轴承故障诊断模型

往期精彩内容: Python-凯斯西储大学(CWRU)轴承数据解读与分类处理 基于FFT CNN - BiGRU-Attention 时域、频域特征注意力融合的轴承故障识别模型-CSDN博客 基于FFT CNN - Transformer 时域、频域特征融合的轴承故障识别模型-CSDN博客 P…

【总结·反思·汇报·思考02】裸辞后,我的一些感想和感悟。

Hello,大家好! 首先,我需要向大家道个歉,对不起!因为最近发生了一些事情,博客文章一直没有更新。(90度鞠躬道歉) 那么,最近到底发生了什么呢?相信大家已经从…

解密分布式锁:保障系统一致性的关键

作者:后端小肥肠 🍇 我写过的文章中的相关代码放到了gitee,地址:xfc-fdw-cloud: 公共解决方案 🍊 有疑问可私信或评论区联系我。 🥑 创作不易未经允许严禁转载。 目录 1. 前言 2. 为何要使用分布式锁&…

HarmonyOS-高级(一)

文章目录 一次开发、多端部署自由流转 🏡作者主页:点击! 🤖HarmonyOS专栏:点击! ⏰️创作时间:2024年12月09日12点19分 一次开发、多端部署 布局能力 自适应布局 拉伸能力均分能力占比能力缩放…

河工oj第七周补题题解2024

A.GO LecturesⅠ—— Victory GO LecturesⅠ—— Victory - 问题 - 软件学院OJ 代码 统计 #include<bits/stdc.h> using namespace std;double b, w;int main() {for(int i 1; i < 19; i ) {for(int j 1; j < 19; j ) {char ch; cin >> ch;if(ch B) b …

开源架构安全深度解析:挑战、措施与未来

开源架构安全深度解析&#xff1a;挑战、措施与未来 一、引言二、开源架构面临的安全挑战&#xff08;一&#xff09;代码漏洞 —— 隐藏的定时炸弹&#xff08;二&#xff09;依赖项安全 —— 牵一发而动全身&#xff08;三&#xff09;社区安全 —— 开放中的潜在危机 三、开…

Ubuntu上使用system()函数运行不需要输入密码

使用system()运行一些终端命令的时候&#xff0c;需要sudo权限&#xff0c;也就是必须输入密码&#xff0c;那么在程序自启动的时候就无法成功启动。如果设置Ubuntu下所有操作都不需要密码&#xff0c;安全性太低&#xff0c;所以我们可以将需要用到的终端指令给予无需输入密码…

HBuilderX(uni-app)Vue3路由传参和接收路由参数!!

uni-app搭建小程序时候Vue3语法接收路由参数&#xff0c;去官方文档查看&#xff0c;是onLoad的option接收参数&#xff0c;我试过&#xff0c;接收不到&#xff0c;上网查各种方法也是不太行&#xff0c;最后自己琢磨出来了&#xff0c;这参数藏得还挺深&#xff01;&#xff…

操作系统(1)OS的基本概念

一、定义 操作系统&#xff08;OS&#xff09;是控制和管理整个计算机系统的硬件与软件资源&#xff0c;并合理地组织、调度计算机的工作与资源的分配&#xff0c;进而为用户和其他软件提供方便接口与环境的程序集合。它是计算机系统中最基本的系统软件。 二、功能 资源管理&am…

gridcontrol多行表头

效果如下 只需这样做,设置该属性为对应的值

Formality:set_svf命令

相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 svf文件的全称是Setup Verification for Formality&#xff0c;即Design Compiler提供给Formality的设置验证文件&#xff0c;它的作用是为Formality的指导模式(Gui…

【蓝桥杯每日一题】重新排序

重新排序 2024-12-8 蓝桥杯每日一题 重新排序 前缀和 差分 题目大意 给定一个数组 A 和一些查询 L i , R i Li_,R_i Li,​Ri​, 求数组中第 L i L_i Li​至第 R i R_i Ri​个元素之和。 小蓝觉得这个问题很无聊, 于是他想重新排列一下数组, 使得最终每个查 询结果的和尽可能…