synchronized锁

synchronized
  • 类锁:给类的静态方法加上synchronized 关键字进行修饰,
    • 锁的是当前类class,一个静态同步方法拿到锁,其他静态同步方法就会等待
    • 静态同步方法和普通同步方法间是没有竞争的
  • 对象锁:给类的方法加上synchronized 关键字进行修饰
    • 锁的是当前对象 this,如果一个对象里有多个synchronized 方法,某个时刻只能有一个线程去调用这个对象其中的一个方法
    • 没有加synchronized的方法不受影响
    • 如果是不同的对象,锁就不同了,也就不会互相干扰
  • 同步代码块锁
    • 锁的是 synchronized () 内的对象
  • 加锁可以保证线程安全,但是会带来性能的下降,在高并发下,能不加锁就不要加锁,一定要加锁也要使加锁的粒度尽可能的小

为什么每一个对象都可以成为锁

  • 在hotsport虚拟机中,monitor采用的是ObjectMonitor实现的,每一个对象天生都带着一个监视器对象,每一个被锁住的对象都会和monitor关联起来
  • monitor 的本质是依赖于操作系统的 Mutex Lock 实现,操作系统实现线程的切换需要在用户态和内核态之间切换,成本很高
  • ObjectMonitor对象重要的属性
    • owner属性记录了持有ObjectMonitor对象的线程id
    • count 初始值为0,表示当前锁对象是否被锁定,加锁就加1,释放锁就减1
    • recursions 初始值为0,表示重入次数
    • entryList 阻塞队列,用于存放阻塞的线程
    • waitSet 等待队列,存放等待的线程

synchronized

  • 由对象头中的 mark word 根据锁标志位的不同来表示锁的状态
  • 在java5之前,只有synchronized ,是操作系统级别的重量级操作,涉及到用户态和内核态的切换,如果锁竞争激烈,性能下降严重
    • java的线程是映射到操作系统的原生线程之上的,如果要阻塞或者唤起一个线程就需要操作系统的接入,就需要在用户态和内核态之间切换
    • 这种切换是很消耗系统资源的,因为用户态和内核态都有自己专用的内存空间、寄存器等,用户态切换至内核态需要传递很多参数和变量给内核,内核也需要保存好用户态的一些变量,以便内核态调用结束后切换回用户态继续工作
    • 所以如果同步代码块中的内容很简单,有可能用户态和内核态之间的切换时间比代码本身的执行时间还长
  • 所以 java6之后,通过引入轻量级锁和偏向锁,来减少获得锁和释放锁所带来的性能消耗,从而优化了synchronized
synchronized 的锁升级
  • 无锁,对象新建出来,还没有和任何synchronized关联,就是无锁的状态
  • 偏向锁:mark word 前54位存储偏向的线程id
  • 轻量级锁
  • 重量级锁
  • 锁升级的过程就是,先cas自旋,实在得不到再阻塞

流程如下:
在这里插入图片描述

偏向锁
  • 一个 synchronized 方法被一个线程抢到了锁,这个方法所在的对象就会在 mark word 中修改偏向锁的标志位,同时前54位也会用来存储线程指针,也就是偏向线程id

  • 偏向模式,如果不存在其他线程竞争,那么持有偏向锁的线程永远不需要进行同步,也就是说,一段同步代码块,如果一直被一个线程多次访问,那么该线程后续的访问会自动获得锁

  • 因为HotSpot作者研究发现,多线程的情况下,大多数时候,锁不仅不存在竞争关系,还存在锁由同一个线程多次获得的情况

  • 所以只需要锁在第一次被拥有的时候,记录下线程的id,这样偏向线程会一直持有锁,这个线程后续进入和退出听不代码块的时候,不需要再次加锁和释放锁,而是去检查 mark word 中的偏向线程id是不是自己

    • 如果是那么锁偏向于当前线程,就不需要再去尝试获得锁了,会直接进入同步块,不需要每次都通过CAS更新对象头,如果自始至终都只有一个线程持有锁,那么偏向锁几乎没由额外的开销,性能极高
    • 如果偏向线程id不是当前线程,表示发生了竞争,表示锁已经不是总偏向于一个线程了,这个时候,会尝试使用CAS来更新 mark word 里的线程id为当前线程的id
      • 如果CAS竞争成功, mark word 里的线程id 就会替换为当前线程的id,锁也不会升级,仍然是偏向锁,只不过是从一个线程偏向到另一个线程
      • 但是如果CAS竞争失败,这个时候就有可能需要 撤销偏向锁,升级为轻量级锁,使线程间公平竞争
  • 偏向锁会偏向于第一个访问到锁的线程,且只有偏向锁被其他线程竞争,持有偏向锁的线程才会释放锁,否则线程是不会主动释放锁的,而对于持有偏向锁的线程也就不需要触发同步,就能在没有资源竞争的情况下消除了同步语句

  • jdk6之后,默认就开启了偏向锁

    • 但是 HotSpot 虚拟机在启动后有个 4s 的延迟才会对每个新建的对象开启偏向锁模式,这四秒钟之内默认会进入轻量级锁
    • 因为 JVM 启动时会进行一系列的复杂活动,比如装载配置,系统类初始化等等。在这个过程中会使用大量synchronized关键字对对象加锁,且这些锁大多数都不是偏向锁。为了减少初始化时间,JVM默认延时加载偏向锁

偏向锁的撤销

  • 只有发生竞争时,偏向锁才会释放,原本持有偏向锁的线程才会被撤销
  • 撤销需要等待全局安全点,也就是该时间点上没有字节码正在执行,同时检查持有偏向锁的线程是否还在执行
  • 如果线程正在执行同步方法,则升级锁
    • 其他线程就尝试使用CAS来更新 mark word 里的线程id,从而抢夺锁,偏向锁就会被取消掉并升级为轻量级锁
    • 轻量级锁仍然由原本的线程A持有,A会继续执行同步代码,其他正在竞争的线程会进入自旋等待重新获取轻量级锁
  • 如果线程执行完了,就会释放锁,并将对象头设置为无锁状态,并撤销偏向锁,被其他线程抢占,重新偏向

java15后会逐步废弃偏向锁

  • 在java15之前,偏向锁是默认开启的
  • 但是15之后,就默认不在开启了,除非手动开启
轻量级锁
  • 关闭偏向锁功能或者多线程竞争偏向锁都会导致偏向锁升级为轻量级锁,升级为轻量锁,会把偏向锁标记改为0,并设置标志位为00

  • 对象头 mark word 前62位用来记录线程的id,后两位为锁的标志位 00

  • 轻量级锁在没有多线程的竞争下,通过cas来代替重量级锁,较少性能的消耗,能够在线程近乎交替执行同步代码块时提高性能

  • 轻量级锁升级过程

    • 首先线程A拿到锁,这时候的锁是偏向锁,偏向于A
    • 线程B又来抢夺锁,发现锁对象头的 当前线程id 不是自己,线程B就会通过CAS操作去修改线程id希望能获得锁
    • 如果B获得成功,也就是A已经执行完了 ,B会把 mark word 当前线程id设置为自己,锁仍然是偏向锁,只是重新偏向于B
    • 如果B获取失败,也就是锁仍然被其他线程占用,锁就会升级为轻量锁,轻量级锁仍然由之前持有锁的线程继续持有,B线程会自旋等待获取轻量级锁

轻量级锁的加锁

  • JVM会为每个线程在当前线程的栈帧中创建用于存储锁记录的空间,称为 Displaced Mark word
  • 如果一个线程获得锁时发现是轻量级锁,会把当前锁的 Mark word 复制到自己的 Displaced Mark word 里
  • 然后线程尝试用CAS将锁的 Mark word 替换为指向锁记录的指针,
    • 如果成功,就代表获取到了锁,
    • 如果失败,表示 Mark word 已经被替换为了其他线程的锁记录,说明存在其他线程竞争锁,当前线程就会尝试使用自旋来获取锁

轻量级锁的释放

  • 释放锁时,当前线程会使用CAS将 Displaced Mark word 的内容复制到锁的 Mark word 中
  • 如果没有发生竞争这个复制操作就会成功,如果有其他线程因为自旋多次导致轻量级锁升级为了重量级锁,那么CAS操作会失败,此时会释放锁并释放被阻塞的线程

轻量级锁和偏向锁的区别:

  • 偏向锁是没有竞争关系的,轻量级锁存在锁的竞争,竞争失败,会自旋尝试抢占锁
  • 偏向锁只有竞争发生才会释放锁,轻量级锁每次退出同步代码块时都需要释放锁
重量级锁
  • 对象头 mark word 前62位用来指向互斥量 (重量级锁) 的指针,后两位为锁的标志位 10

  • 当线程自旋达到一定次数,仍然没有获得锁,也就是有大量线程在竞争锁,那么就会升级锁为重量级锁

  • jdk6之前默认是自旋次数达到10次,或者自旋线程数超过cpu核数的一半,都会升级为重量级锁

  • jdk7增加了自适应自旋锁,也就是自旋的次数变的不在固定,

    • 通过同一个锁上一次自旋的时间,和拥有锁线程的状态来决定
    • 也就是如果线程自旋成功了,那么下次自旋的最大次数就会增加,因为JVM认为上次成功了,那么这次也有很大概率成功
    • 反之如果很少会自旋成功,那么下次就会减少自旋的次数甚至不自旋,来避免cpu空转

synchronized

  • 对于同步代码块

    • 一般情况下,一把锁,会有一个monitorenter指令,和两个monitorexit指令

    • 加锁会执行monitorenter

    • 释放锁会执行monitorexit,如果产生异常也会执行monitorexit,所以synchronized产生异常也可以释放锁

  • 对于同步方法

    • 会加上 ACC_SYNCHRONIZED 标识,代表这个方法是同步方法
    • 如果方法持有ACC_SYNCHRONIZED 标识,执行前就会去获取 monitor ,执行完再释放monitor
  • 对于静态同步方法

    • 会加上 ACC_SYNCHRONIZED 和 ACC_STATIC 标识,用于区分类锁和对象锁

synchronized 加锁流程

  • 当执行monitorenter时,
    • 如果锁计数器为0,就说明锁没有被其他线程持有,虚拟机会将当前线程设置为锁的持有线程,并且把锁计数器加1,重入次数加1,然后执行同步代码块的业务代码
    • 如果锁计数器不为0
      • 且持有线程是当前线程,虚拟机会把锁计数器加1,
      • 如果不是,就需要进入当前锁对象的阻塞队列,等待其他线程释放锁
  • 当执行monitoreixt时,虚拟机会把锁计数器减1,当锁计数器为0时,会擦除锁的持有线程,这样就释放了锁

锁升级到轻量级锁,重量级锁后,mark word中保存的就分别是线程栈帧里的锁记录指针和重量级锁指针,不在保存hashcode 和GC的年龄,这些信息的去向是:

  • 首先,java中一个对象如果计算过一次哈希码,就应该保持这个值不变,除非用户手动重载hashcode方法就可以返回任意值,如果哈希码经常变动,会导致很多依赖哈希码的对象都存在风险

  • 绝大多数对象的哈希码都来自于 object 的 hashcode 方法,通过在对象头中存储计算结果来保证第一次计算过后,在次调用hashcode 方法取到的哈希码就永远不会改变

  • 无锁状态下,mark word 中可以存储对象的 hash code 值,当对象的 hashcode 方法第一次被调用调用时,JVM就会生成对应的hash code值并存储到 mark word 中

  • 对于偏向锁,在线程获取偏向锁时,会使用线程id和epoch 值覆盖 hash code值所在的位置,所以如果一个对象已经计算过了哈希码,这个对象就无法被设置为偏向锁

    • 因为如果允许的话,会导致hash code 值和线程id相互覆盖,导致前后调用hashcode 方法的计算结构不一致,所以偏向锁和哈希码不共存
    • 所以如果一个对象处于偏向锁状态(锁是偏向锁,但是已经释放过了锁),被调用hashcode方法后,会直接膨胀为轻量级锁
    • 如果一个对象处于偏向锁过程中(锁是偏向锁,且没有释放锁),被调用hashcode方法后,会直接膨胀为重量级锁
  • 而对于轻量级锁,JVM会在当前线程的栈帧中创建一个锁记录的空间,用于存放锁对象的mark word 拷贝,这个拷贝中就包含了hash code值和GC的年龄,释放锁后会将这些信息写回对象头

  • 升级到重量级锁后,mark word 保存了重量级锁的ObjectMonitor 类里有字段记录非加锁状态下的mark word ,锁释放后信息也会被写回到对象头

synchronized 锁的优缺点

  • 偏向锁的优点是:加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距,缺点是如果线程间存在竞争,会带来额外的锁撤销的消耗,所以只适用于只有一个线程访问同步块的场景
  • 轻量级锁的优点是:即使存在线程的竞争也不会阻塞,提高了程序的响应速度,缺点是始终拿不到锁的线程自旋会消耗cpu,适用于追求响应时间,同步块执行时间很短的场景
  • 重量级锁,线程间的竞争不会消耗cpu去自旋,缺点是线程阻塞会导致响应缓慢,适用于追求吞吐量,同步块执行时间较长的情况

锁消除(同步省略)

  • 在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断,同步块锁,是否有加锁的必要,如果没有就可以不考虑同步,也就是所谓的锁消除
  • 因为加锁的代价是很高的,消除锁可以大大提高并发性和性能,这种情况字节码文件依然会有加锁操作,但是执行的时候会去掉
    public static void test4() {Object obj =new Object();//例如这里,每个线程进来都会new一个对象,每个线程都有一个锁,没有意义synchronized (obj){System.out.println("锁消除案例");}}

锁粗化

  • 如果对同一个对象执行了连续的加锁和解锁的操作,那么 JIT 会将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,避免频繁的加锁和解锁
    static Object lock =new Object();public static void test5() {new Thread(()->{synchronized (lock){System.out.println("业务1");}synchronized (lock){System.out.println("业务2");}synchronized (lock){System.out.println("业务3");}},"锁粗化案例").start();}

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

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

相关文章

elasticsearch如何操作索引库里面的文档

上节介绍了索引库的CRUD,接下来操作索引库里面的文档 目录 一、添加文档 二、查询文档 三、删除文档 四、修改文档 一、添加文档 新增文档的DSL语法如下 POST /索引库名/_doc/文档id(不加id,es会自动生成) { "字段1":"值1", "字段2&q…

基于头脑风暴算法优化的Elman神经网络数据预测 - 附代码

基于头脑风暴算法优化的Elman神经网络数据预测 - 附代码 文章目录 基于头脑风暴算法优化的Elman神经网络数据预测 - 附代码1.Elman 神经网络结构2.Elman 神经用络学习过程3.电力负荷预测概述3.1 模型建立 4.基于头脑风暴优化的Elman网络5.测试结果6.参考文献7.Matlab代码 摘要&…

Vue3 的 emit 该怎么写, vue2 对比

Vue3 的 emit 该怎么写&#xff0c; vue2 对比 这是个新手问题&#xff0c;从 vue2 转到 vue3 之后&#xff0c;一时间不知道该怎么用它了。 vue2 用法 vue2 在 template 中 和 在方法中的用法如下&#xff1a; <template><button click"$emit(clicked, 要传…

贝锐花生壳全新功能:浏览器一键远程访问SSHRDP远程桌面

为了满足特定场景的远程访问需求&#xff0c;如&#xff1a;远程群晖NAS设备、远程SQL Server数据库/MySQL数据库、3389远程桌面&#xff08;RDP远程桌面&#xff09;、远程SSH、我的世界游戏联机…… 贝锐花生壳推出了场景映射服务&#xff0c;不仅提供满足相应场景的网络带宽…

在 2024 年搜索中提升排名的 7 项内容调整

忘掉关键词填充和算法追逐。2024 年的重点是 EEAT&#xff0c;宝贝&#xff01;谷歌希望最专业、最权威、最值得信赖&#xff08;EEAT&#xff09;的内容能够排名靠前&#xff0c;这就意味着您的内容需要成为专业知识、参与度和信任度的交响乐。 准备好让搜索引擎和人类都无法…

YOLOv5算法进阶改进(10)— 更换主干网络之MobileViTv3 | 轻量化Backbone

前言:Hello大家好,我是小哥谈。MobileViTv3是一种改进的模型架构,用于图像分类任务。它是在MobileViTv1和MobileViTv2的基础上进行改进的,通过引入新的模块和优化网络结构来提高性能。本节课就给大家介绍一下如何在主干网络中引入MobileViTv3网络结构,希望大家学习之后能够…

基于Java SSM框架实现四六级在线考试系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现四六级在线考试系统演示 摘要 随着现在网络的快速发展&#xff0c;网上管理系统也逐渐快速发展起来&#xff0c;网上管理模式很快融入到了许多学院的之中&#xff0c;随之就产生了“四六级在线考试系统”&#xff0c;这样就让四六级在线考试系统更加方便…

kbdnso.dll文件缺失,软件或游戏报错的快速修复方法

很多小伙伴遇到电脑报错&#xff0c;提示“kbdnso.dll文件缺失&#xff0c;程序无法启动执行”时&#xff0c;不知道应该怎样处理&#xff0c;还以为是程序出现了问题&#xff0c;想卸载重装。 首先&#xff0c;先要了解“kbdnso.dll文件”是什么&#xff1f; kbdnso.dll是Win…

2007-2022年上市公司数字化转型数据(区分年报和管理层讨论)(含原始数据+处理代码+结果)

2007-2022年上市公司数字化转型数据&#xff08;年报和管理层讨论&#xff09;&#xff08;含原始数据处理代码结果&#xff09; 1、时间&#xff1a;2007-2022年 2、指标&#xff1a;统计年度、证券代码、人工智能技术、区块链技术、云计算技术、大数据技术、数字技术应用、…

requests库中Session对象超时解决过程

引言 在使用Python进行网络请求时&#xff0c;requests库是一个非常常用的工具。它提供了Session对象来管理和持久化参数&#xff0c;例如cookies、headers等。但是&#xff0c;对于一些需要长时间运行的请求&#xff0c;我们需要设置超时时间来避免长时间等待或者无限期阻塞的…

浏览器使用隧道代理HTTP:洞悉无界信息

在信息爆炸的时代&#xff0c;互联网已经成为获取信息的首选渠道。然而&#xff0c;在某些地区或情况下&#xff0c;访问某些网站可能会受到限制。这时&#xff0c;隧道代理HTTP便成为了一个重要的工具&#xff0c;帮助用户突破限制&#xff0c;洞悉无界信息。 一、隧道代理HT…

上海AI lab大模型微调

教程链接&#xff1a;InternLM学习教程链接 命令行演示结果&#xff1a; web演示结果

书生·浦语大模型全链路开源开放体系

书生浦语大模型全链路开源开放体系 大模型成为热门关键词书生浦语大模型开源历程书生浦语20B开源大模型性能从模型到应用书生浦语全链路开源开放体系数据预训练微调评测部署智能体 大模型成为热门关键词 大模型成为发展通用人工智能的重要途径 书生浦语大模型开源历程 书生浦语…

Apipost多Host服务配置

最近Apipost新增同环境下多host服务的配置功能&#xff0c;本篇文章带来该功能的使用场景及使用方法。 配置方法&#xff1a; 点击右上角眼睛标识进入环境管理 点击添加服务&#xff0c;输入服务名和URL 配置完成后需要在接口目录中选择该目录下需要使用的host服务&#xff0…

架构设计系列之分布式系统 11,12,13,14,15,16,17,18

架构设计系列之分布式系统 11&#xff1a;架构理论 第二部分 引 言 前面的几部分介绍了关于软件架构设计的基本概念、基本理论、演化史、常见架构相关的内容&#xff0c;同时还专门介绍了架构设计相关的组织文化保障、遵循定律以及一个程序员应该如何转型成为架构师&#xf…

灰度发布及声明式资源管理(yaml文件)

一、三种常见的项目发布方式 1&#xff09;蓝绿发布 2&#xff09;灰度发布【常用】 3&#xff09;滚动发布 应用程序升级&#xff0c;面临最大的问题是新旧业务之间的切换 立项-定稿-需求发布-开发-测试-发布&#xff0c;测试上线后&#xff0c;再完美也会有问题&#xff0c;为…

基于回溯搜索算法优化的Elman神经网络数据预测 - 附代码

基于回溯搜索算法优化的Elman神经网络数据预测 - 附代码 文章目录 基于回溯搜索算法优化的Elman神经网络数据预测 - 附代码1.Elman 神经网络结构2.Elman 神经用络学习过程3.电力负荷预测概述3.1 模型建立 4.基于回溯搜索优化的Elman网络5.测试结果6.参考文献7.Matlab代码 摘要&…

50、实战 - 利用 conv + bn + relu + add 写一个残差结构

上一节介绍了残差结构,还不清楚的同学可以返回上一节继续阅读。 到了这里,一个残差结构需要的算法基本都介绍完了,至少在 Resnet 这种神经网络中的残差结构是这样的。 本节我们做一个实战,基于之前几节中手写的 conv / bn 算法,来搭建一个残差结构。其中,relu 的实现和…

EasyCode代码生成器插件

EasyCode文档&#xff1a;https://gitee.com/makejava/EasyCode/wikis/pages?sort_id725187&doc_id166248 EasyCode 优点 可以生成controller层的代码。可以一次性生成多张表的各层代码。可以自定义模板。 EasyCode使用 在插件市场下载easy code插件 在idea中进行数据…

MySQL中的事务到底是怎么一回事儿

简单来说&#xff0c;事务就是要保证一组数据库操作&#xff0c;要么全部成功&#xff0c;要么全部失败。在MySQL中&#xff0c;事务支持是在引擎层实现的&#xff0c;但并不是所有的引擎都支持事务&#xff0c;如MyISAM引擎就不支持事务&#xff0c;这也是MyISAM被InnoDB取代的…