synchronized 的锁类型

        之前的文章有讲过对同步锁的理解,实现同步锁的方式无非是多个线程抢占一个互斥变量,如果抢占成功则表示获得了锁,而没有获得锁的线程则阻塞等待,直到获得锁的线程释放锁

        如图所示,在Mark Word中,我们发现锁的类型有偏向锁、轻量级锁、重量级锁,那么
    
        其实,在JDK 1.6之前,synchronized只提供了重量级锁的机制,重量级锁的本质就是我们前面对于锁的认知,也就是没有获得锁的线程会通过park方法阻塞,接着被获得锁的线程唤醒后再次抢占锁,直到抢占成功。
        重量级锁依赖于底层操作系统的Mutex Lock来实现,而使用Mutex Lock需要把当前线程挂起,并从用户态切换到内核态来执行,这种切换带来的性能开销是非常大的。因此,如何在性能和线程安全性之间做好平衡,就是一个值得探讨的话题了。
        在JDK1.6之后,synchronized做了很多优化,其中针对锁的类型增加了偏向锁和轻量级锁,这两种锁的核心设计理念就是如何让线程在不阻塞的情况下达到线程安全的目的。

偏向锁的原理分析


        偏向锁其实可以认为是在没有多线程竞争的情况下访问synchronized修饰的代码块的加锁场景,也就是在单线程执行的情况下。
        很多读者可能会有疑问,没有线程竞争,那为什么要加锁呢?实际上对程序开发来说,加锁是为了防范线程安全性的风险,但是是否有线程竞争并不由我们来控制,而是由应用场景来决定。假设这种情况存在,就没有必要使用重量级锁基于操作系统级别的Mutex Lock来实现锁的抢占,这样显然很耗费性能。
        所以偏向锁的作用就是,线程在没有线程竞争的情况下去访问synchronized 同步代码块时。会尝试先通过偏向锁来抢占访问资格,这个抢占过程是基于CAS来完成的,如果抢占锁成功,则直接修改对象头中的锁标记。其中,偏向锁标记为1,锁标记为01,以及存储当前获得锁的线程ID。而偏向的意是就是,如果线程X获得了偏向锁,那么当线程x后续再访问这个同步方法时,只需要判断对象头中的线程ID和线程X是否相等即可。如果相等,就不需要再次去抢占锁,直接获得访问资格即可,其实现原理如图所示,


        结合前面关于对象头部分的说明及偏向锁的原理,我们通过一个例子来看一下偏向锁的实现。

public class BiasedLockExample {
    public static void main(String[] args) {
        BiasedLockExample example=new BiasedLockExample(); 
        System.out.println("加锁之前");
        System.out.println(ClassLayout.parseInstance(example).toPrintable()); 
        synchronized (example){
            System.out.println("加锁之后");
            System,out.print1n(ClassLayout .parseInstance(example).toPrintable());
        }
    }
}

        在上述代码中,BiasedLockExample演示了针对example这个锁对象,在加锁之前和加锁之后分别打印对象的内存布局的过程,来看一下输出结果。

        从上述输出结果中我们发现:

  • 在加锁之前,对象头中的第一个字节00000001最后三位为[001],其中低位的两位表示锁标记,它的值是[01],表示当前为无锁状态。
  • 在加锁之后,对象头中的第一个字节01111000最后三位为[000],其中低位的两位是[00],对照前面介绍的MarkWord中的存储结构的含义,它表示轻量级锁状态。

        当前的程序并不存在锁竞争,基于前面的理论分析,此处应该是获得偏向锁,但是为什么变成了轻量级锁呢?
        原因是,JVM在启动的时候,有一个启动参数-XX:BiasedLockingStartupDelay,这个参数表示偏向锁延迟开启的时间,默认是4秒,也就是说在我们运行上述程序时,偏向锁还未开启,导致最终只能获得轻量级锁。之所以延迟启动,是因为JVM在启动的时候会有很多线程运行,也就是说会存在线程竞争的场景,那么这时候开启偏向锁的意义不大。


        如果我们需要看到偏向锁的实现效果,那么有两种方法:

  • 添加JVM启动参数-XX:BiasedLockingStartupDelay=0,把延迟启动时间设置为0。
  • 抢占锁资源之前,先通过Thread.sleep)方法睡眠4秒以上。

        最终得到如下输出结果。


        从上面输出结果我们发现,加锁之后,第一个字节低位部分的3位变成了[101],高位[1]表示当前是偏向锁状态,低位[01]表示当前是偏向锁状态,这显然达到了我们的预期效果。细心的读者会发现,加锁之前的锁标记位也是[101] -------- 这里并没有加偏向锁呀?
        我们来分析一下,加锁之前并没有存储线程ID,加锁之后才有一个线程ID(45889541)。因此,在获得偏向锁之前,这个标记表示当前是可偏向状态,并不代表已经处于偏向状态。

05 00 00 00  (00000101 000000000000000000000000)        (5)
05 38 bc 02  (00000101 001110001011110000000010)        (45889541)

轻量级锁的原理分析

        在线程没有竞争时,使用偏向锁能够在不影响性能的前提下获得锁资源,但是同一时刻只允许一个线程获得锁资源,如果突然有多个线程来访问同步方法,那么没有抢占到锁资源的线程要怎么办呢?很显然偏向锁解决不了这个问题。
        正常情况下,没有抢占到锁的线程肯定要阻塞等待被唤醒,也就是说按照重量级锁的逻辑来实现,但是在此之前,有没有更好的平衡方案呢?于是就有了轻量级锁的设计。
        所谓的轻量级锁,就是没有抢占到锁的线程,进行一定次数的重试(自旋)。比如线程第一次没抢到锁则重试几次,如果在重试的过程中抢占到了锁,那么这个线程就不需要阻塞,这种实现方式我们称为自旋锁,具体的实现流程如图所示。


        当然,线程通过重试来抢占锁的方式是有代价的,因为线程如果不断自旋重试,那么CPU会一直处于运行状态。如果持有锁的线程占有锁的时间比较短,那么自旋等待的实现带来性能的提升会比较明显。反之,如果持有锁的线程占用锁资源的时间比较长,那么自旋的线程就会浪费CPU资源,所以线程重试抢占锁的次数必须要有一个限制。
        在JDK1.6中默认的自旋次数是10次,我们可以通过-XX:PreBlockSpin参数来调整自旋次数。同时开发者在JDK1.6中还对自旋锁做了优化,引入了自适应自旋锁,自适应自旋锁的自旋次数不是固定的,而是根据前一次在同一个锁上的自旋次数及锁持有者的状态来决定的。如果在同一个锁对象上,通过自旋等待成功获得过锁,并且持有锁的线程正在运行中,那么JVM会认为此次自旋也有很大的机会获得锁,因此会将这个线程的自旋时间相对延长。反之,如果在一个锁对象中,通过自旋锁获得锁很少成功,那么JVM会缩短自旋次数。
轻量级锁的演示在上面中有,默认不修改偏向锁的延期开启参数,加锁得到的锁状态就是轻量级锁。

重量级锁的原理分析

        轻量级锁能够通过一定次数的重试让没有获得锁的线程有可能抢占到锁资源,但是轻量级锁
得销节省积巷有销的时间较短的情况下才能起到提升同步锁性能的效果。如果持有锁的线程占用锁资源的时间较长,那么不能让那些没有抢占到锁资源的线程不断自旋,否则会占用过多的CPU资源,这反而是一件得不偿失的事情。
        如果没抢占到锁资源的线程通过一定次数的自旋后,发现仍然没有获得锁,就只能阻塞等待了,所以最终会升级到重量级锁,通过系统层面的互斥量来抢占锁资源。重量级锁的实现原理如图 所示。


        整体来看,我们发现,如果在偏向锁、轻量级锁这些类型中无法让线程获得锁资源,那么这些没获得锁的线程最终的结果仍然是阻塞等待,直到获得锁的线程释放锁之后才能被唤醒。而在整个优化过程中,我们通过乐观锁的机制来保证线程的安全性。
        下面这个例子演示了在加锁之前、单个线程抢占锁、多个线程抢占锁的场景中,对象头中的锁的状态变化。

public class HeavyLockExample {

    public static void main(String[] args) throws InterruptedException {
        HeavyLockExample heavy = newHeavyLockExample(); 
        System,out.println("加锁之前");
        System.out.println(ClassLayout.parseInstance(heavy),toPrintable()); 
        Thread t1=new Thread(()->{ 
            synchronized (heavy){
                try{
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printstackTrace( );
                }
            }
         });
        t1.start();
        //确保t1 线程已经运行
        TimeUnit.MILLISECONDS.sleep(500); 
        system.out.println("t1 线程抢占了锁");
        System.out,println(ClassLayout.parseInstance(heavy).toPrintable()); 
        synchronized (heavy){
            system.out.println("main 线程来抢占锁");
            system. out.println(ClassLayout.parseInstance(heavy).toprintable());
        }
    }
}

上述程序打印的结果如下:

        从上述打印结果来看,对象头中的锁状态一共经历了三个类型。

  • 加锁之前,对象头中的第一个字节是00000001,表示无锁状态。
  • 当t1线程去抢占同步锁时,对象头中的第一个字节变成了11011000,表示轻量级锁状态。
  • 接着 main 线程来抢占同一个对象锁,由于t1线程睡眠了2秒,此时锁还没有被释放,main线程无法通过轻量级锁自旋获得锁,因此它的锁的类型是重量级锁,锁标记为10。

        注意,在这个案例演示中,开发者并没有开启偏向锁的参数,如果开启了,那么第一个加锁之后得到的锁状态应该是偏向锁,然后直接到重量级锁(因为tI线程有一个sleep,所以轻量级锁肯
定无法获得)。

        由此可以看到,synchronized同步锁最终的底层加锁机制是JVM层面根据线程的竞争情况逐步升级来实现的,从而达到同步锁性能和安全性平衡的目的,而这个过程并不需要开发者干预。

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

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

相关文章

wangeditor 富文本编辑器使用

使用环境vue3 ts &#xff0c;添加了字数限制 尝试了两种&#xff0c;使用方法类似&#xff08;参考文档&#xff09;&#xff0c;工具栏图标有不同&#xff0c;最后选用了第一种。 一、wangeditor 安装 npm i wangeditor --save 使用 这里封装了个简单组件 <templat…

IO流框架,缓冲流

一.缓冲流有什么优点 Java中的缓冲流&#xff08;Buffered Stream&#xff09;具有以下优势&#xff1a; 提高效率&#xff1a;缓冲流通过在内存中缓存一部分数据&#xff0c;减少了直接从内存到磁盘或从磁盘到内存的频繁IO操作&#xff0c;从而提高了读写效率。缓冲区大小调整…

macOS 12 Monterey v12.7.1正式版:开启全新的操作系统体验

macOS 12 Monterey已经向所有兼容的Mac设备推出&#xff0c;为您带来了一系列强大的新功能和改进。这个全新的操作系统版本&#xff0c;不仅带来了更流畅的用户体验&#xff0c;还增强了与iOS设备的无缝集成&#xff0c;让您的设备使用更加高效&#xff0c;更加便捷。 macOS 1…

RabbitMQ生产者的可靠性

目录 MQ使用时会出现的问题 生产者的可靠性 1、生产者重连 2、生产者确认 3、数据持久化 交换机持久化 队列持久化 消息持久化 LazyQueue懒加载 MQ使用时会出现的问题 发送消息时丢失&#xff1a; 生产者发送消息时连接MQ失败生产者发送消息到达MQ后未找到Exchange生…

Vue 3.3.6 ,得益于WeakMap,比之前更快了

追忆往昔&#xff0c;穿越前朝&#xff0c;CSS也是当年前端三剑客之一&#xff0c;风光的很&#xff0c;随着前端跳跃式的变革&#xff0c;CSS在现代前端开发中似乎有点默默无闻起来。 不得不说当看到UnoCss之前&#xff0c;我甚至都还没听过原子化CSS[1]这个概念&#xff08;…

时序预测 | Matlab实现ARIMA-LSTM差分自回归移动模型结合长短期记忆神经网络时间序列预测

时序预测 | Matlab实现ARIMA-LSTM差分自回归移动模型结合长短期记忆神经网络时间序列预测 目录 时序预测 | Matlab实现ARIMA-LSTM差分自回归移动模型结合长短期记忆神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 | Matlab实现ARIMA-LSTM差…

springboot配置https

SSL &#xff1a; secure socket layer 是一种加密协议&#xff0c;SSL主要用于保护数据在 客户端和服务器之间的传输&#xff0c;&#xff0c;防止未经授权的访问和窃取敏感信息 在腾讯云申请ssl证书 申请了之后在我的域名中&#xff0c;&#xff0c;解析 解析了之后&…

Jmeter的接口自动化测试

在去年实施了一年的三端&#xff08;PC、无线M站、无线APP【Android、IOS】&#xff09;后&#xff0c;今年7月份开始&#xff0c;我们开始进行接口自动化的实施&#xff0c;目前已完成了整个框架的搭建以及接口的持续测试集成。今天做个简单的分享。 在开始自动化投入前&#…

虚拟化 vs. 裸金属:K8s 部署环境架构与特性对比

伴随着 IT 云化转型的逐步推进&#xff0c;越来越多的用户加入应用容器化改造的行列&#xff0c;并使用 Kubernetes&#xff08;K8s&#xff09;进行容器部署管理。然而&#xff0c;令不少用户感到困惑的是&#xff0c;由于大部分应用此前都部署在虚拟化或超融合环境&#xff0…

轻量封装WebGPU渲染系统示例<7>-材质多pass(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/version-1.01/src/voxgpu/sample/MultiMaterialPass.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 2. 高频调用与低频调用隔离。 3. 面向用户的易用性封装。 4. 渲染数据和渲染机制分离。 …

Mac电脑窗口管理Magnet中文 for mac

Magnet是一款Mac窗口管理工具&#xff0c;它可以帮助用户轻松管理打开的窗口&#xff0c;提高多任务处理效率。以下是Magnet的一些主要特点和功能&#xff1a; 分屏模式支持&#xff1a;Magnet支持多种分屏模式&#xff0c;包括左/右/顶部/底部 1/2 分屏、左/中/右 1/3 分屏、…

基于51单片机的温度测量报警系统的设计与制作

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、实习目的二、实习任务2.1 设计温度测量报警系统硬件电路2.2 温度测量报警系统软件编程、仿真与调试&#xff1b;2.3 完成温度测量报警系统的实物制作与调试…

基于定容积法标准容器容积标定中的电动针阀自动化解决方案

摘要&#xff1a;在目前的六氟化硫气体精密计量中普遍采用重量法和定容法两种技术&#xff0c;本文分析了重量法中存在的问题以及定容法的优势&#xff0c;同时也指出定容法在实际应用中还存在自动化水平较低的问题。为了提高定容法精密计量过程中的自动化水平&#xff0c;本文…

从工厂到社会:探索如何应用设计模式工厂模式

文章目录 &#x1f31f; 将设计模式工厂模式运用到社会当中&#x1f34a; 工厂模式在社会中的应用&#x1f389; 工厂&#x1f389; 餐厅&#x1f389; 运输 &#x1f34a; 工厂模式的优势&#x1f389; 代码简洁&#x1f389; 扩展性强&#x1f389; 便于维护和管理 &#x1f…

信钰证券:华为汽车概念股持续活跃 圣龙股份斩获12连板

近期&#xff0c;华为轿车概念股在A股商场遭到热捧&#xff0c;多只股票迭创前史新高。10月23日&#xff0c;华为轿车概念股再度走强&#xff0c;到收盘&#xff0c;板块内圣龙股份、银宝山新涨停&#xff0c;轿车ETF在重仓股提振下盘中一度上涨近2%。业界人士认为&#xff0c;…

Day13力扣打卡

打卡记录 奖励最顶尖的 k 名学生(哈希表排序) 用哈希表对所有的positive与negative词条进行映射&#xff0c;然后遍历求解。tip&#xff1a;常用的分割字符串的操作&#xff1a;1.stringstream配合getline() [格式buf, string, char]2.string.find()[find未找到目标会返回npos…

javaEE -9(7000字详解TCP/IP协议)

一&#xff1a; IP 地址 IP地址&#xff08;Internet Protocol Address&#xff09;是指互联网协议地址&#xff0c;又译为网际协议地址。 IP地址是IP协议提供的一种统一的地址格式&#xff0c;它为互联网上的每一个网络和每一台主机分配一个逻辑地址&#xff0c;以此来屏蔽物…

AOP 笔记

AOP【面向切面编程】 作用&#xff1a;在不惊动原始设计的基础上进行功能增强。 无侵入式编程 连接点&#xff1a;程序执行的任意位置&#xff0c;SpringAOP中&#xff0c;理解为方法的执行。 切入点&#xff1a;匹配连接点的式子,要追加功能的方法 通知&#xff08;写在通…

服务运营 |论文解读: 住院病人“溢出”:一种近似动态规划方法

摘要 在住院床位管理中&#xff0c;医院通常会将住院病人分配到相对应的专科病房&#xff0c;但随着病人的入院和出院&#xff0c;可能会出现病人所需的专科病房满员&#xff0c;而其他病房却有空余床位的情况。于是就有了 "溢出 "策略&#xff0c;即当病人等候时间…

【目标检测】Visdrone数据集和CARPK数据集预处理

之前的博文【目标检测】YOLOv5跑通VisDrone数据集对Visdrone数据集简介过&#xff0c;这里不作复述&#xff0c;本文主要对Visdrone数据集和CARPK数据集进行目标提取和过滤。 需求描述 本文需要将Visdrone数据集中有关车和人的数据集进行提取和合并&#xff0c;车标记为类别0&…