共享模型之管程(三)

news2024/9/24 15:18:50

1.Synchronized优化原理

1.1.轻量级锁(Lock Record)

1.1.1.简介

1>.轻量级锁的使用场景:如果一个对象虽然有多个线程访问,但是多个线程访问的时间是错开的(即没有竞争),那么可以使用轻量级锁来进行优化;

2>.轻量级锁对使用者是透明的,即语法仍然是"synchronized";

1.1.2.案例

1>.有两个方法同步块,利用同一个对象加锁.代码如下:

public class TestSynchronized {
    static final Object obj = new Object();

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

    public static void method2(){
        synchronized (obj){
            //同步代码块
        }
    }
}

2>.分析:

①.线程执行到第一个synchronized(obj)时,会在栈帧中创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定的对象的Mark Word信息;
在这里插入图片描述
②.让锁记录中Object Reference指向锁对象Object,并尝试用CAS替换Object的Mark Word,将Mark Word的值存入锁记录;
使用CAS将锁记录中的"lock record地址和状态00"与Object对象头中的"Mark Word"进行交换;
在这里插入图片描述
③.如果CAS替换成功,那么Object对象头中就存储了"锁记录地址和状态00",表示由该线程给对象加锁;
状态'00'表示轻量级锁;
在这里插入图片描述
④.如果CAS替换失败,有两种情况:

  • 如果是其他线程已经持有了该Object的轻量级锁(即对象头中的Mark Word中的锁状态标记不是’01’或者对象头中的Mark Word中的锁状态标记已经变成’00’,而且锁记录地址指向的是其他线程),这时表明有竞争,进入锁膨胀过程;
  • 如果是线程自己执行了synchronized锁重入(即在同步代码块中调用了另一个同步代码块/另一个包含同步代码块的方法),那么在一个新的栈帧中再添加一条锁记录Lock Record作为重入的计数,然后重复②,③步骤,但是本次加锁会失败,可是本次失败并不会对代码的执行产生影响,最终第二次创建的锁记录中的锁记录地址的值是空对象null,表示本次使用锁重入机制;
    在这里插入图片描述

⑤.(当第二个synchronized(obj)同步代码块执行完毕)当退出synchronized代码块(解锁)时,如果发现有锁记录地址的值为null的锁记录,表示有重入,这时重置锁记录(将该锁记录删除),表示重入计数减1;
在这里插入图片描述
⑥.(当第一个synchronized(obj)同步代码执行完毕)当退出synchronized代码块(解锁)时,锁记录中锁记录地址的值不为null,这时会使用CAS将锁记录中之前替换过来的Mark Word的值恢复/还原给Object对象头;

  • 如果恢复成功,则解锁成功;
  • 如果恢复失败,说明轻量级锁进行了锁膨胀或者已经升级为重量级锁,再进入重量级锁的解锁流程;

⑦.最后清除锁记录;

1.2.锁膨胀(Monitor)

1.2.1.简介

1>.如果线程在尝试给对象加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其他线程为此对象加上了轻量级锁,产生了竞争,这时需要进行锁膨胀,将轻量级锁变成重量级锁;

1.2.2.案例

1>.代码如下:

public class TestSynchronized {

    static Object obj = new Object();

    public static void method1(){
        synchronized (obj){
            //同步代码块
        }
    }
}

2>.分析:

①.当线程Thread-1进行轻量级加锁时,线程Thread-0已经对该对象加上了轻量级锁;
在这里插入图片描述
此时线程Thread-1加轻量级锁失败,进入锁膨胀流程:

  • 为Object对象申请Monitor锁(重量级锁),让Object对象指向重量级锁的地址(Object对象头中的Mark Word变成/设置Monitor对象的地址,并且锁状态标识从’00’变成’10’),
  • 之后线程Thread-1自己进入Monitor对象的EntryList等待队列中进行BLOCKED阻塞,等待被唤醒;
    在这里插入图片描述
  • 当线程Thread-0执行完同步代码块解锁时,使用CAS将锁记录中之前替换过来的Mark Word的值恢复给Object对象的对象头,此时必然恢复失败,这时就会进入重量级锁的解锁流程,即按照Object对象头中Monitor地址找到Monitor对象,设置Monitor对象的Owner属性值为null,然后唤醒EntryList等待队列中处于BLOCKED阻塞状态的线程竞争锁,而竞争的过程是非公平的(并不一定是先到先得);

1.3.自旋优化

1.3.1.简介

1>.多个线程在重量级锁(Monitor)竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即循环重试获取Monitor锁的过程中持锁线程退出了同步块,释放了锁),那么当前线程就可以避免阻塞,直接成为Monitor锁的所有者;

某个线程获取锁的时候,发现Monitor对象的Owner已经指向了其他的线程,即Monitor锁被其他线程占用了,那么这个线程先不进入EntryList等待队列中进行BLOCKED阻塞,而是循环重试几次获取Monitor锁,如果在循环重试获取锁的过程中,其他持锁线程执行完同步代码块中的代码,释放了Monitor锁,那么当前线程就可以避免阻塞,直接成为Monitor锁的所有者;如果循环重试几次之后,其他持锁线程一直没有执行完同步代码块的代码,一直没有释放锁,那么当前线程最终还是会进入EntryList等待队列中进行BLOCKED阻塞;

2>.自旋重试成功的情况:
在这里插入图片描述
注意:由上图可知,自旋优化只有在多核CPU环境下才能发挥作用,在单核CPU环境下没有效果!

3>.自旋重试失败的情况:
在这里插入图片描述
注意:

①.在Java6之后自旋锁是自适应的,比如线程刚刚的一次自旋操作成功过,那么就可以认为本次自旋成功的可能性会高,那么本次就会多自旋几次;反之,就会减少自旋次数甚至不自旋,总之,就是比较智能;

②.自旋会占用CPU时间片,单核CPU环境下自旋就是浪费,没有意义,多核CPU环境下自旋才能发挥优势;

③.Java7之后不能控制是否开启自旋功能(由jvm底层控制);

1.4.偏向锁

1.4.1.简介

1>.轻量级锁在没有竞争的时候(就自己这个线程),每次重入仍然需要执行CAS替换操作;

2>.Java6中引入了偏向锁来做进一步优化: 只要第一次加锁时成功使用CAS操作将线程ID设置到对象的Mark Word头中,那么之后再次加锁时发现对象头的Mark Word中这个线程ID是自己的就表示没有竞争,不用重新CAS操作.以后只要不发生竞争,这个锁对象就归该线程所有(即对象头的MarkWord中会一直存放该线程ID);

1.4.2.案例

1>.代码如下:

public class TestSynchronized2 {
    static final Object obj = new Object();

    public static void m1(){
        synchronized (obj){
            //同步块A
            m2();

        }
    }
    public static void m2(){
        synchronized (obj){
            //同步块B
            m3();
        }
    }

    public static void m3(){
        synchronized (obj){
            //同步块C
        }
    }
}

2>.分析:

在这里插入图片描述
在这里插入图片描述

1.4.3.偏向状态

回忆一下对象头格式:
在这里插入图片描述

第一行是无锁状态;
第二行是偏向锁;
第三行是轻量级锁;
第四行是重量级锁;

倒数第三位的"biased_lock"是偏向锁的状态,'0’表示没有启用偏向锁,'1’表示启用了偏向锁;

1>.说明: 一个对象创建时

①.如果开启了偏向锁(默认开启),那么对象创建之后,MarkWord值为’0x05’,即最后三位为’101’(对象处于一个可以偏向的状态),这时它的thread,epoch,age都是’0’;

②.偏向锁默认是延迟的,不会在程序启动时立即生效,如果想要避免延迟,可以通过JVM参数’-XX:BiasedLockingStartupDelay=0’来禁用延迟;

③.如果没有开启偏向锁,那么对象创建之后,MarkWord值为’0x01’,即最后三位为’001’,这时它的hashcode,age都是’0’,第一次用到hashcode时才会赋值;

④.处于偏向锁的对象解锁之后,线程ID仍然存储在对象头中;

2>.代码示例:

@Slf4j
public class TestSynchronized3 {
    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();
        //使用jol工具类打印对象信息
        log.info(ClassLayout.parseInstance(dog).toPrintable());
    }
}

class Dog{
}

在这里插入图片描述
在这里插入图片描述
注意:查看对象的对象头信息需要借助一个工具'jol',在maven项目中添加其依赖即可!

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

3>.禁用偏向锁

①.修改程序的jvm启动参数为"-XX:-UseBiasedLocking";

②.在启用偏向锁的状态下,在加锁之前调用"对象实例.hashcode()"也会禁用偏向锁;

  • 因为调用了’对象实例.hashcode()‘会将对象的hashcode填充到对象头的MarkWord中,由于此时MarkWord中存储了偏向锁要使用的线程ID(54位),导致对象的hashcode没有存储空间(位置)了,所以需要撤销对象的偏向状态(MarkWord值后三位从’101’变成’001’),清除偏向锁对应的信息,对象恢复到原始状态;

③.在启用了轻量级锁的状态下,调用’对象实例.hashcode()'并不会禁用轻量级锁,因为使用轻量级锁时对象的hashcode存放在线程栈帧的锁记录中;

④.在启用了重量级级锁的状态下,调用’对象实例.hashcode()'并不会禁用重量级锁,因为使用重量级锁时对象的hashcode存放在monitor对象中;

1.4.4.偏向锁撤销

1>.当有其它线程使用偏向锁对象(/处于偏向状态的对象)时,会将偏向锁升级为轻量级锁;

2>.示例代码:

@Slf4j
public class TestSynchronized3 {
    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();

        new Thread(() -> {
            //加锁前打印对象信息
            log.info(ClassLayout.parseInstance(dog).toPrintable());

            //加锁后打印对象信息
            synchronized (dog) {
                log.info(ClassLayout.parseInstance(dog).toPrintable());
            }

            //解锁后打印对象信息
            log.info(ClassLayout.parseInstance(dog).toPrintable());

            synchronized (TestSynchronized3.class){
                //t2线程运行的时需要等待t1线程运行完毕才能继续运行,保证两个线程是错开运行的
                //t1线程执行完毕之后,唤醒正在等待中的线程;
                TestSynchronized3.class.notify();
            }
        }, "t1").start();

        new Thread(() -> {
            //t2线程运行的时需要等待t1线程运行完毕才能继续运行,保证两个线程是错开运行的
            synchronized (TestSynchronized3.class){
                try {
                    TestSynchronized3.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            log.info("线程t2运行....");
            //加锁前打印对象信息
            log.info(ClassLayout.parseInstance(dog).toPrintable());

            //加锁后打印对象信息
            synchronized (dog) {
                log.info(ClassLayout.parseInstance(dog).toPrintable());
            }

            //解锁后打印对象信息
            log.info(ClassLayout.parseInstance(dog).toPrintable());
        }, "t2").start();
    }
}

class Dog {
}

在这里插入图片描述
在这里插入图片描述
结论:

当有其它线程使用偏向锁对象(/处于偏向状态的对象)时,会将偏向锁升级为轻量级锁!

注意: 调用wait/notify也可以撤销偏向锁;

1.4.5.批量重偏向

1>.如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,重偏向会重置对象的Thread ID;

2>.当撤销偏向锁阈值超过20次后,JVM会这样觉得,我是不是偏向错了呢(偏向T1)?于是会在给这些对象加锁时重新偏向至加锁线程(偏向T2);

当撤销偏向锁阈值超过20次之后,对象就会一直使用偏向锁;

3>.代码示例

@Log4j2
public class TestSynchronized4 {
    public static void main(String[] args) throws InterruptedException {
        Vector<Dog> list = new Vector<>();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 30; i++) {
                Dog d = new Dog();
                list.add(d);
                synchronized (d) {
                    log.info(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
            }
            synchronized (list) {
                list.notify();
            }
        }, "t1");
        t1.start();

        Thread t2 = new Thread(() -> {
            synchronized (list) {
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("===============> ");
            for (int i = 0; i < 30; i++) {
                Dog d = list.get(i);
                log.info(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                synchronized (d) {
                    log.info(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
                log.info(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
        }, "t2");
        t2.start();
    }
}

class Dog{}

在这里插入图片描述

1.4.6.批量撤销

1>.当撤销偏向锁阈值超过40次后,JVM会这样觉得,自己确实偏向错了,根本就不该偏向.于是整个类的所有对象都会变为不可偏向的(对象恢复到最原始状态),新建的对象也是不可偏向的;
在这里插入图片描述

1.4.7.锁消除

public class MyBenchmark {
 static int x = 0;
 
 public void a() throws Exception {
    x++;
 }
 
 //Java程序在运行的时候,JVM底层会通过一个JIT(即时编译器)对Java字节码做一步优化,
 //其中一个优化手段就是分析局部变量(锁对象)是否可以优化,如果局部变量(锁对象)的作用范围
 //不会发生逃逸/暴露,这就意味着该局部变量(锁对象)不可能被多个线程共享,此时对该局部变量(锁
 //对象)加锁是没有任何意义的,也就是说这里的synchronized并不会消耗系统性能.
 //这就是锁消除优化;
public void b() throws Exception {
    Object o = new Object();
        synchronized (o) {
           x++;
}
  }
}

关闭锁消除优化的JVM参数: -XX:-EliminateLocks

1.4.8.锁粗化

1>.对相同对象多次加锁,导致线程发生多次重入,可以使用锁粗化方式来优化;

2>.示例代码:

//频繁的进行加锁、解锁,会造成性能上的损失.如果虚拟机探测到有一串零碎操作都是对同一对象加锁,将会把加锁同步的范围扩展到整个操作序列的外部
for(int i=0;i<100000;i++){  
    synchronized(this){  
    }
}  

//在锁粗化之后运行逻辑如下列代码
//锁粗化就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,避免频繁的加锁解锁操作
synchronized(this){  
    for(int i=0;i<100000;i++){  
    }
}  

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

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

相关文章

docker减少构建镜像大小

目录 1.原镜像大小 1.1 Dockerfile文件 1.2 hello文件 1.3 进入文件夹myprojecthello打包镜像 1.4查看打包的镜像 2.通过拆分文件夹减少镜像大小 2.1 创建两个文件夹 2.2 移动文件 2.3 打包镜像 3. 通过 .dockerignore 文件的方式 3.1 创建 world.txt文件 3.2 创建 …

【Spring(三)】DI入门案例(XML版)

文章目录前言DI入门案例总结前言 前面我们已经演示了IOC入门案例的介绍&#xff0c;里边还有一些东西是耦合的&#xff0c;接下来我们就来学习DI的入门案例来解决这个问题&#x1f4aa;&#x1f4aa;。 DI入门案例 我们先来想一下&#xff0c;你做DI这个首先得先让IOC容器管着b…

STM32 TIM PWM中阶操作:互补PWM输出

STM32 TIM PWM中阶操作详解&#xff1a;互补PWM输出 STM32 TIM可以输出管脚PWM信号适合多种场景使用&#xff0c;功能包括单线/非互补PWM输出&#xff0c;双线/互补PWM输出&#xff0c;以及死区时间和刹车控制等。 实际上&#xff0c;因为早期IP Core的缺陷&#xff0c;早期的…

万应低代码12月重点更新内容速递

速览版 详情版 低代码开发效率升级 01 动作流 动作编排过程中涉及到多条件判断时使用&#xff0c;即&#xff1a;满足某条件可执行一条分支&#xff0c;不满足则执行“其他”分支。 【使用场景】 ● 以“个人所得税计算”场景为例&#xff0c;不同收入水平的人输入不同的收…

人工智能对联生成 API 数据接口

人工智能对联生成 API 数据接口 基于百万数据训练&#xff0c;AI 训练与应答&#xff0c;多结果返回。 1. 产品功能 AI 基于百万历史对联数据训练应答模型&#xff1b;机器学习持续训练学习&#xff1b;一个上联可返回多个下联应答&#xff1b;毫秒级响应性能&#xff1b;数据…

关键字:final

文章目录一、final修饰类修饰方法修饰变量修饰属性修饰局部变量static final练习每日一考一、final final&#xff1a;最终的 final可以修饰的结构&#xff1a;类、方法、变量 修饰类 此类不能被其他类所继承 修饰方法 此方法不可以被重写 修饰变量 此时的“变量”就称为…

ACL论文总结

「博士毕业一年&#xff0c;我拿下 ACL Best Paper」 在不久前结束的自然语言处理NLP&#xff0c;领域顶级学术会议ACL2021上&#xff0c;字节跳动AL lab研究院许晶晶&#xff0c;完成了他的演讲。 在全球顶会做完分享后&#xff0c;许晶晶感到很欣慰&#xff0c;没想到&#…

自然语言处理重点 第11章 机器阅读理解 复习

机器阅读理解复习机器阅读理解概述机器阅读(MRC)理解与问答系统(QA)的区别:本章内容&#xff1a;MRC 任务分类&#xff1a;完形填空形式&#xff08;cloze-style&#xff09;选项形式片段抽取形式&#xff08;span extraction&#xff09;文本生成形式&#xff08;free-answer/…

uCharts柱状图横向排列及不同条件下得数据颜色不同,雷达图的使用及各个参数的配置讲解

一:雷达图 1,建立一个盒子,内部存放uCharts图表 在data数据中return内部配置如下数据: chartData: {},//您可以通过修改 config-ucharts.js 文件中下标为 [radar] 的节点来配置全局默认参数,如都是默认参数,此处可以不传 opts 。实际应用过程中 opts 只需传入与全局默认…

【台式机DIY】我的第一台台式机电脑配置清单

文章目录[toc]【第一台台式机】一、电脑配置清单【电脑小白我科普】一.CPU1.选购&#xff1a;主流品牌2.选购&#xff1a;如何选择3.参数&#xff1a;接口4.参数&#xff1a;频率5.参数&#xff1a;核心和线程6.参数&#xff1a;功耗7.参数&#xff1a;缓存二.主板1.选购&#…

Django开发员工管理系统(Part I)

文章目录1. 准备工作1.1 创建django项目1.2 创建app1.3 配置settings.py文件&#xff0c;完成app注册2. 设计数据库表结构3. 在MySQL中生成表3.1 创建数据库3.2 修改配置文件&#xff0c;连接MySQL数据库3.3 通过django命令生成数据库表4. 编写部门列表4.1 &#xff08;前段页面…

报表开发工具FastReport.NET的十大常见问题及解决方法(二)

Fastreport是目前世界上主流的图表控件&#xff0c;具有超高性价比&#xff0c;以更具成本优势的价格&#xff0c;便能提供功能齐全的报表解决方案&#xff0c;连续三年蝉联全球文档创建组件和库的“ Top 50 Publishers”奖。慧都科技是Fast Reports在中国区十余年的友好合作伙…

php宝塔搭建部署实战海洋cms视频内容管理系统源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 本期给大家带来一套php开发的海洋cms视频内容管理系统源码&#xff0c;感兴趣的朋友可以自行下载学习。 技术架构 PHP7.2 nginx mysql5.7 JS CSS HTMLcnetos7以上 宝塔面板 文字搭建教程 下载源码&#…

在动态规划的海洋中遨游(一)

前言&#xff1a;\textcolor{Green}{前言&#xff1a;}前言&#xff1a; &#x1f49e;本专栏用于本人刷算法的过程。主要包含刷题中的感受以及知识点缺陷。对于学习者来说可以作为参考。 目前更新的算法内容会比较多&#xff0c;很多都是通过刷题来进行知识点的总结&#xff0…

计算机SCI论文,很难发表吗?应该如何发表? - 易智编译EaseEditing

首先&#xff0c;找题目需要符合国际标准&#xff0c;但不要缺少创新的探究题目。这个题目可以是最新的技术&#xff0c;也可以是最新的领域&#xff1b; 也可以是探索过很多次的课题。但是如果我们想成功交付&#xff0c;如何创新是我们需要思考的。 其次&#xff0c;因为英语…

为什么有的电源不是从0V开始上电的

大家可以看下&#xff0c;这张图片是测试XILINX 的FPGA 325T的上电时序图&#xff0c;其中绿色的线是FPGA 核心电源VCCINT 1.0V的波形&#xff0c;黄色的是BANK的电源2.5V的波形&#xff0c;蓝色的是辅助电源1.8V 的波形大家有没有发现这个时序图中黄色的波形&#xff0c;也就是…

Java之多线程详解

目录 一、线程简介 进程&#xff08;Process &#xff09;与 线程&#xff08;Thread&#xff09; 二、线程创建 1、线程Thread 1.1. 步骤 1.2 应用 1.3 案例&#xff1a;下载图片 2、实现Runnable接口 2.1 步骤 2.2 应用 3.小结 3. 实现Callable接口&#xff08;了解…

java开发的考研系统大学生考研推荐网站考研网站源码

简介&#xff1a; 考研信息推荐查询。主要是管理发布管理考研的知识文章&#xff0c;或者上传资料&#xff0c;发布考研的视频。学生可以注册后下载资料&#xff0c;查看考研文章视频等。文章分为vip文章和普通文章&#xff0c;学生查看vip文章需要消耗积分。 演示视频 https…

FX5U 原点回归指令 DSZR

上一篇文章中转述了网友的文章&#xff0c;因回原点实在重要&#xff0c;再详细描述DSZR指令。 DSZR是具有自动搜索功能的原点回归指令。它对当前位置没有要求&#xff0c;在任意位置哪怕是停止在限位开关位置上都能完成原点回归操作。 1.指令格式 S1 原点回归速度或存储了数…

linux虚拟机搭建kafka(单节点、使用kafka自带zookeeper)

本文使用kafka单节点安装及配置&#xff0c;并使用kafka自带的zookeeper。一般kafka需要起三个kafka构成集群&#xff0c;可以连单独的zookeeper&#xff0c;本文不涉及。一、kafka下载解压安装包下载地址&#xff1a;https://archive.apache.org/dist/kafka/2.5.0/kafka_2.12-…