【JUC】二十九、synchronized锁升级之轻量锁与重量锁

文章目录

  • 1、轻量锁
  • 2、轻量锁的作用
  • 3、轻量锁的加锁和释放
  • 4、轻量级锁的代码演示
  • 5、重量级锁
  • 6、重量级锁的原理
  • 7、锁升级和hashcode的关系
  • 8、锁升级和hashcode关系的代码证明
  • 9、synchronized锁升级的总结
  • 10、JIT编译器对锁的优化:锁消除和锁粗化
  • 11、结语

📕相关笔记:

【synchronized锁升级之 无锁】
【synchronized锁升级之 偏向锁】

1、轻量锁

前面一篇提到偏向锁,即只有一个线程在竞争,此时通过资源类对象的对象头的Mark Word来标记,避免了用户态和内核态的频繁切换。

在这里插入图片描述

再往下,又来了一个线程也来竞争这个锁,且此时这两个线程近乎可以错开交替执行(或者说同步代码块/方法执行一次时间很短,哪怕另一个线程等,也不会等太久),如下图的1、2、3、4标号:

在这里插入图片描述

这就是轻量级锁的出现场景:有线程来参与竞争了,但不存在锁竞争太过激烈的情况,获取锁的冲突时间极端,本质就是CAS自旋锁,不要直接往重锁走。对应的共享对象内存图:

在这里插入图片描述

2、轻量锁的作用

轻量锁是为了在两个线程近乎交替执行同步块时来提高性能。

直白说就是先CAS自旋,不行了再考虑升级为重锁,使用操作系统的互斥量。升级到轻量锁的时机有:

  • 关闭了偏向锁
  • 多线程竞争偏向锁,可能导致偏向锁升级为轻量锁(这里写可能,是因为如果恰好是一个线程over,一个线程上位,则依旧是偏向锁)

举个例子:比如现有A线程拿到了锁,A一个人走偏向锁玩了一会儿后,线程B来了,B在争抢时发现共享对象的对象头中Mark Word里的线程ID标记不是线程B的ID(而是线程A),此时,B线程通过CAS来尝试修改标记。当:

  • 此时线程A刚好Over,B上位,修改Mark Word里的线程ID为B,此时,仍为偏向锁,且偏向B

在这里插入图片描述

  • 如果A正在执行,B修改失败,则升级为轻量级锁,且轻量级锁继续由原来的线程A持有,接着执行刚才没执行完的,而线程B则自旋等待获取这个轻量级锁

在这里插入图片描述

3、轻量锁的加锁和释放

加锁:

JVM会在线程的栈帧中创建用于存储锁记录Lock Record的空间,称为Displaced Mark Word。

在这里插入图片描述

若一个线程获得锁时发现是轻量级锁,会把对象锁的MarkWord复制到自己的Displaced Mak Word里面。然后线程尝试用CAS将锁的MarkWord替换为指向锁记录的指针。如下面两幅草图示意的变化过程:

在这里插入图片描述

在这里插入图片描述

如果替换成功,当前线程获得轻量锁。如果失败,表示Mark Word已经被替换成了其他线程的锁记录,说明在与其它线程竞争锁,当前线程就尝试使用自旋来获取锁(自旋一定次数后仍未获得锁,升级为重量锁)。

轻量级锁的释放:

在释放锁时,当前线程会使里CAS操作将Displaced Mark Word的内容复制回对象锁的Mark Word里面。如果没有发生竞争。那么这个复制的操作会成功。如果持有锁期间有其他线程因为自旋多次导致轻量级锁升级成了重量级锁,那么CAS操作会失败,此时会释放锁并唤醒被阻塞的线程。

4、轻量级锁的代码演示

-XX:-UseBiasedLocking

添加JVM参数,关闭偏向锁,就可以直接进入轻量级锁:

Object object = new Object();
new Thread(() -> {
    synchronized (object){
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}).start();

运行:

在这里插入图片描述

轻量锁下,自旋达到一定次数或者说程度,会升级为重量锁:

  • Java6之前,默认情况下自旋的次数是10次或者自旋的线程数超过了cpu核数的一半,可-XX:PreBlockSpin=10来修改
  • Java6之后,JVM做了优化,采用自适应自旋

自适应自旋,即线程如果自旋成功了,那下次自旋的最大次数会增加,因为JVM认为既然上次成功了,那么这一次也很大概率会成功。反之,如果很少会自旋成功,那么下次会减少自旋的次数甚至不自旋,以避免CPU空转。直白说就是会总结前人的经验了、会预判走位了。

轻量锁与偏向锁的区别:

  • 偏向锁是一个线程自己在玩,而偏向锁涉及竞争,且争夺轻量级锁失败时,自旋尝试抢占锁
  • 轻量级锁每次退出同步块都需要释放锁(要不就不会是一个走了一个接上了),而偏向锁则只在有线程来竞争时才释放锁

5、重量级锁

竞争太激烈时,只能捅到重量级锁,进行内核态和用户态的切换,但前面偏向锁和轻量级锁已然做了一定程度的缓冲和优化了。

在这里插入图片描述

有大量的线程参与锁的竞争,冲突性很高:

Object object = new Object();
//多个线程
for (int i = 0; i < 6; i++) {
    new Thread(() -> {
        synchronized (object){
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    },String.valueOf(i)).start();
}

运行:

在这里插入图片描述

6、重量级锁的原理

Java中synchronized的重量级锁,是基于进入和退出Monitor对象实现的。在编译时会将同步块的开始位置插入monitor enter指令,在结束位置插入monitor exit指令。

当线程执行到monitor enter指令时,会尝试获取对象所对应的Monitor所有权,如果获取到了,即获取到了锁,会在Monitor的owner中存放当前线程的id,这样它将处于锁定状态,除非退出同步块,否则其他线程无法获取到这个Monitor。

7、锁升级和hashcode的关系

在这里插入图片描述

可以看到,无锁状态下,Java对象头的Mark Word中是有空间存hashcode的,锁升级后,则没有位置了,那要是锁升级后hashcode去哪儿了 ?

在这里插入图片描述

总结下:

1) 在无锁状态下,Mark Word可以存储对象的identity hash code值,当对象的hashCode()方法第一次被调用时,JVM会生成对应的identity hash code值,存于对象头的Mark Word中。

2) 对于偏向锁,在线程获取偏向锁时,用Thread Id和epoch值(看成时间戳)去覆盖identity hash code所在的位置。如果一个对象的hashcode()方法已经被调用过一次,则这个对象不能被设置偏向锁,因为如果可以,那identity hash code就会被线程ID覆盖,就会造成同一对象,前后两次调用hashcode方法得到的结果不一致。

3) 升级为轻量锁时,JVM会在当前线程的栈帧中创建一个锁记录空间Lock Record(前面已提到),用于拷贝和存储锁对象的Mark Word,里面自然包含了identity hash code、GC年龄等,且释放轻量锁时,这些数据又会写回对象头,因此轻量级锁可以和identity hash code共存。

4) 到重量级锁时,Mark Word保存的是重量级锁指针,而代表重量级锁的ObiectMonitor类里有字段记录了非加锁状态下的Mark Word,锁释放以后也会写回对象头。

8、锁升级和hashcode关系的代码证明

Case1:当一个对象已经计算过identity hashcode,它就无法进入偏向锁状态,会跳过偏向锁,直接升级轻量级锁

//先睡5秒,抵消偏向锁开启的延时,保证开启偏向锁
TimeUnit.SECONDS.sleep(5);
Object object = new Object();
System.out.println("这里应该是偏向锁==>");
System.out.println(ClassLayout.parseInstance(object).toPrintable());
//没有重写hashcode,重写后无效
int hashCode = object.hashCode();
//验证当一个对象已经计算过identity hash code后,就无法进入偏向状态
new Thread(() -> {
    synchronized (object){
        System.out.println("这里本应是偏向锁,但刚才计算过一致性哈希hashcode,这里会直接升级为轻量级锁 ==>");
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}).start();

在这里插入图片描述

Case2:偏向锁过程中遇到一致性哈希计算请求,立马撤销偏向模式,膨胀为重量级锁

//先睡5秒,抵消偏向锁开启的延时,保证开启偏向锁
TimeUnit.SECONDS.sleep(5);
Object object = new Object();
synchronized (object){
    System.out.println(ClassLayout.parseInstance(object).toPrintable());
    System.out.println("此时是偏向锁,但下面一计算哈希,会立马撤销偏向模式,膨胀为重量级锁");
    //计算哈希值,这里的hashcode方法是没有重写过的
    int hashCode = object.hashCode();
    System.out.println(ClassLayout.parseInstance(object).toPrintable());
}

在这里插入图片描述

9、synchronized锁升级的总结

在这里插入图片描述

synchronized锁升级,目的还是实现一个性能优化,思想就是:先自旋,不行了再阻塞。一直都是围绕尽量避免内核态和用户态频繁切换来展开的。实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式。太精辟了这句!道出了这几种锁的关系。

在这里插入图片描述

另外,synchronized在修饰方法和代码块时,在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的。JDK1.6之前synchronized使用的是重量级锁,JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁。

请添加图片描述
最后 :

  • 偏向锁:适用于单线程的情况,在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁。
  • 轻量级锁:适用于竞争较不激烈的情况(这和乐观锁的使用范围类似),轻量级锁采用的是自旋锁,如果同步方法/代码块执行时间很短的话(就很容易一个线程完事儿了,另一个线程尚未,哪怕不是这么刚刚好,也自旋等不了太久),采用轻量级锁自旋虽然会占用Cpu资源,但是相对比使用重量级锁还是更高效。
  • 重量级锁:适用于竞争激烈的情况,如果同步方法/代码块执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁一更严重,这时候就需要升级为重量级锁。

10、JIT编译器对锁的优化:锁消除和锁粗化

JIT,即Just Time Compiler,翻译:即时编译器。

synchronized锁消除

以下是一个简单的synchronized代码,没啥毛病(别说优化成线程池):

public class LockClearDemo {

    static Object objectLock = new Object();

    public void m1(){
        synchronized (objectLock){
            System.out.println("----hello clearDemo");
        }
    }
    public static void main(String[] args) {
        LockClearDemo lockClearDemo = new LockClearDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                lockClearDemo.m1();
            },String.valueOf(i)).start();
        }
    }
}

但此时,做出这样一个修改:

在这里插入图片描述

这么写,看似有synchronized,语法也没报错,实际每个线程进来都new一个自己的object对象,相当于是每一个线程一个自己创造的锁,而不是正常的所有线程共同抢一个对象的锁,因此,这么写毫无意义,JIT编译器会无视它,极端的说就是根本没有加这个锁对象的底层的机器码,是消除了锁的使用。

synchronized锁粗化

看示例代码:

public class LockDemo {

    static Object objectLock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (objectLock){
                System.out.println("111111");
            }
            synchronized (objectLock){
                System.out.println("222222");
            }
            synchronized (objectLock){
                System.out.println("333333");
            }
            synchronized (objectLock){
                System.out.println("444444");
            }
        }).start();
    }
    
}

注意,这不是可重入锁,这里是频繁加锁解锁。虽然无语法错误,但底层编译器会把它合并优化为:

在这里插入图片描述

锁粗化:假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块,加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能。

11、结语

  • 没有锁:自由自在
  • 偏向锁:唯我独尊
  • 轻量锁:楚汉争霸
  • 重量锁:群雄逐鹿

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

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

相关文章

保障网络安全:了解威胁检测和风险评分的重要性

在当今数字时代&#xff0c;网络安全问题变得愈发突出&#xff0c;而及时发现和迅速应对潜在威胁成为保障组织信息安全的首要任务。令人震惊的是&#xff0c;根据2023年的数据&#xff0c;平均而言&#xff0c;检测到一次网络入侵的时间竟然长达207天。这引起了对安全策略和技术…

MPLS专线和互联网专线有什么区别?如何选择?

MPLS和互联网专线是什么&#xff1f; MPLS专线和互联网专线是企业网络连接的常见方式。MPLS专线基于多协议标签交换&#xff08;MPLS&#xff09;该技术利用专线连接两个或多个分支机构&#xff0c;提供高质量的数据传输服务。互联网专线是基于公共知识产权基础设施的连接方式…

Python实现高效摸鱼,批量识别银行卡号并自动写入Excel表格

前言 每当有新员工入职&#xff0c;人事小姐姐都要收集大量的工资卡信息&#xff0c;并且生成Excel文档&#xff0c;看到小姐姐这么辛苦&#xff0c;我就忍不住要去帮她了… 于是我用1行代码就实现了自动识别银行卡信息并且自动生成Excel文件&#xff0c;小姐姐当场就亮眼汪汪…

ChatGPT一周年,一图总结2023生成式AI里程碑大事件时间线

带你探索AI的无限可能&#xff01;AI一日&#xff0c;人间一年&#xff0c;这句话绝非空谈&#xff01; AI技术在不断地发展&#xff0c;让我们一起期待它未来更多的可能性吧&#xff01; 2022 年 11 月 30 日&#xff0c;OpenAI 宣布正式推出 ChatGPT。365 天过去&#xff0c;…

羊大师解读提高免疫力,能从羊奶开始吗?

羊大师解读提高免疫力&#xff0c;能从羊奶开始吗&#xff1f; 在当今充满挑战的世界中&#xff0c;拥有强大的免疫力是保持健康的关键。免疫系统是我们身体的守护者&#xff0c;能够抵御病菌和疾病&#xff0c;使我们远离健康问题。而如何提高免疫力一直是人们关注的焦点。近…

‘BLEUUID‘ does not name a type错误怎么解决?

摘要&#xff1a;arduino环境下对esp32蓝牙编程时会遇到BLEUUID does not name a type错误&#xff0c;本文介绍解决方法。 硬件设备是安信可ESP32-S模组。 错误发生在代码最开始的地方&#xff0c;include了一个蓝牙设备头文件&#xff0c;然后定义了UUID&#xff0c;注意看&a…

Java序列化、反序列化-为什么要使用序列化?Serializable接口的作用?

什么是序列化和反序列化&#xff1f; 把对象转换成字节序列把字节序列恢复成对象 结合OSI七层协议模型&#xff0c;序列化和反序列化是在那一层做的&#xff1f; 在OSI七层模型中&#xff0c;序列化工作的层级是表示层。这一层的主要功能包括把应用层的对象转换成一段连续的二进…

Vue3-18-侦听器watch()、watchEffect() 的基本使用

什么是侦听器 个人理解&#xff1a;当有一个响应式状态&#xff08;普通变量 or 一个响应式对象&#xff09;发生改变时&#xff0c;我们希望监听到这个改变&#xff0c;并且能够进行一些逻辑处理。那么侦听器就是来帮助我们实现这个功能的。侦听器 其实就是两个函数&#xff…

JAVA 版多商家入驻 直播带货 商城系统 B2B2C 之 鸿鹄云商B2B2C产品概述

随着互联网的快速发展&#xff0c;越来越多的企业开始注重数字化转型&#xff0c;以提升自身的竞争力和运营效率。在这个背景下&#xff0c;鸿鹄云商SAAS云产品应运而生&#xff0c;为企业提供了一种简单、高效、安全的数字化解决方案。 鸿鹄云商SAAS云产品是一种基于云计算的软…

Seata配置

参考教程 seata 分布式事务的环境搭建与使用 Seata 1.4.0 nacos配置和使用&#xff0c;超详细 Seata 1.4.2 的安装 Nacos的配置和使用 官网下载地址 本文以v1.4.1为例 1.数据库及表的创建 创建seata数据库&#xff0c;创建以下表&#xff08;右键连接-》新建数据库seata-》…

一文读懂FastAPI:Python 开发者的福音

FastAPI是一个基于Python的现代化Web框架&#xff0c;它提供了快速、简单和高性能的方式来构建API。 它结合了Python的静态类型检查和自动化文档生成的功能&#xff0c;使得开发API变得更加容易和高效。 下面将介绍如何使用FastAPI快速开发接口&#xff0c;并且利用自动生成的…

leetcode做题笔记2132. 用邮票贴满网格图

给你一个 m x n 的二进制矩阵 grid &#xff0c;每个格子要么为 0 &#xff08;空&#xff09;要么为 1 &#xff08;被占据&#xff09;。 给你邮票的尺寸为 stampHeight x stampWidth 。我们想将邮票贴进二进制矩阵中&#xff0c;且满足以下 限制 和 要求 &#xff1a; 覆盖…

OpenKylin安装idea

Web和客户端都支持Linux了&#xff0c;一方面为了Linux上调试程序方便&#xff0c;另一方面为了把开发环境彻底支持Linux&#xff0c;在Linux上安装idea运行代码&#xff0c;之前剔除Maven那些的优点就来了&#xff0c;在OpenKylin上我只要安装idea和jdk就能正常运行代码了。最…

【c】数组元素移动

本题的难点之处就是不让你创建新的数组&#xff0c;而且移动的距离也没有给限制&#xff0c;比如有7个数&#xff0c;本题没有限制必须移动距离小于7&#xff0c;也可能移动的距离大于7&#xff0c;甚至更多&#xff0c;下面附上我的代码 #include<stdio.h>int main() {…

2.两数相加

借文引流&#xff1a;五点钟科技_大道至简系列,机器学习算法系列,学习经验分享-CSDN博客 欢迎大家阅览我的其它专栏。 题目&#xff1a; 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数…

21. python __init__.py 文件的行为

重复打印行为分析 说明结论主模块主模块所在位置不会被python认为是包 说明 我在调试代码的时候&#xff0c;发现上面的print打印了两次&#xff0c;如果将图片中的 from aaa.F import Cat 改成 from F import Cat 则print只会打印一次。这是为什么呢&#xff1f; 结论 from …

每日一题:实现方法fn,遇到退格字符就删除前面的字符,遇到俩个退格就删除俩个字符

每日一题 请按以下要求实现方法fn,遇到退格字符就删除前面的字符&#xff0c;遇到俩个退格就删除俩个字符&#xff1a; // 比较含有退格的字符串&#xff0c;"<-"代表退格键&#xff0c;"<"和"-"均为正常字符 // 输入&#xff1a;"…

OSWBB 部署实现

1、OSWatcher (oswbb) 是一个可供用户下载的工具&#xff0c;可以用来抓取操作系统的性能指标。 是一组shell程序&#xff0c;程序中调用: top, vmstat, iostat, mpstat, netstat,and traceroute等os的监控工具 。OSWatcher 的使用是基于 standard licensing terms 并且不需要…

『OPEN3D』1.8.3 多份点云配准

多份点云配准是将多份点云数据在全局空间中对齐的过程。通常,输入是一组数据(例如点云或RGBD图像){Pi}。输出是一组刚性变换{Ti},使得经过变换的点云在全局空间中对齐。 NNNNNathan 本专栏地址: https://blog.csdn.net/qq_41366026/category_12186023.html 此处是…

最新CRMEB商城源码开源版v5.2.2版本+前端uniapp

CRMEB开源商城系统是一款全开源可商用的系统&#xff0c;前后端分离开发&#xff0c;全部100%开源&#xff0c;在小程序、公众号、H5、APP、PC端都能用&#xff0c;使用方便&#xff0c;二开方便&#xff01;安装使用也很简单&#xff01;使用文档、接口文档、数据字典、二开文…