深入详解Java中synchronized锁升级的套路

 更新时间:2023年04月14日 10:12:57   作者:奔跑的毛球  
synchronized锁是啥?锁其实就是一个对象,随便哪一个都可以,Java中所有的对象都是锁,换句话说,Java中所有对象都可以成为锁。本文我们主要来聊聊synchronized锁升级的套路,感兴趣的可以收藏一下

synchronized锁是啥?锁其实就是一个对象,随便哪一个都可以,Java中所有的对象都是锁,换句话说,Java中所有对象都可以成为锁。
这次我们主要聊的是synchronized锁升级的套路

synchronized会经历四个阶段:无锁状态、偏向锁、轻量级锁、重量级锁依次从耗费资源最少,性能最高,到耗费资源多,性能最差。

锁原理

先看看这些状态的锁为什么称之为锁,他们的互斥原理是啥。

偏向锁

当一个线程到达同步代码块,尝试获取锁对象的时候,会查看对象头中的MarkWord里的线程ID,如果这里没有ID则将自己的保存进去,拿到锁。若是有,则查看是否是当前线程,如果不是,就CAS尝试改,如果是,就已经拿到了锁资源。

这里详细说说CAS尝试修改的逻辑:它会检查持有偏向锁的线程状态。首先遍历当前JVM的所有存活的线程,如果能找到偏向的线程,则说明偏向的线程还存活,此时会检查线程是否在执行同步代码块中的代码,如果是,则升级为轻量级锁,去继续进行CAS竞争锁。所以加了偏向锁之后,同时只有一个线程可以拿到锁执行同步代码块中的代码。

轻量级锁

查看对象头中的MarkWord里的Lock Record指针指向的是否是当前线程的虚拟机栈,如果是,拿锁执行业务,如果不是则进行CAS,尝试修改,若是修改几次都没有成功,再升级到重量级锁。

重量级锁

查看对象头中的MarkWord里的指向的ObjectMonitor,查看owner是否是当前线程,如果不是,扔到ObjectMonitor里的EntryList中排队,并挂起线程,等待被唤醒。

锁升级

无锁

一般情况下,新new出来的一个对象,暂时就是无锁状态。因为偏向锁默认是有延迟的,在启动JVM的前4s中,不存在偏向锁,但是如果关闭了偏向锁延迟的设置,new出来的对象,就会添加一个匿名偏向锁。也就是说这个对象想找一个线程去增加偏向锁,但是没有找到,称之为匿名偏向。存储的线程ID为一堆0000,也没有任何地址信息。

我们可以通过以下配置关闭偏向锁延迟。

//关闭偏向锁延迟的指令
-XX:BiasedLockingStartuoDelay=0

偏向锁

当某一个线程来获取这个锁资源时,此时会成功获取到,就会变为偏向锁,偏向锁存储线程的ID。

偏向锁升级时,会触发偏向锁撤销,偏向锁撤销需要等到一个安全点,比如GC的时候,偏向锁撤销的成本太高,所以默认开始时,会做偏向锁延迟。若是直接有多个线程竞争,会跳过偏向锁,直接变为轻量级锁。

细说一下偏向锁撤销的过程,成本为啥高呢?当一个线程拿到偏向锁之后,会把锁的对象头的Mark Work中的线程id指向自己,当又有一个线程来了进行争抢导致锁升级的的时候,会暂停之前拿到偏向锁的线程,然后清空Mark Work中的线程id增加一个轻量级锁,然后再恢复暂停的线程继续执行。这也是为什么等到安全点再执行锁升级的原因,因为要暂停线程。

常见的安全点:

  • 执行GC的时候
  • 方法返回之前
  • 调用某个方法之后
  • 抛出异常的位置
  • 一个循环的末尾

轻量级锁

当在出现了多个线程的竞争,就会升级为轻量级锁,轻量级锁的效果就是基于CAS尝试获取锁资源,这里会用到自适应自旋锁,根据上次CAS成功与否,耗费的时间,决定这次自旋多少次。

轻量级锁适用于竞争不是很激烈的场景,一个线程拿到锁,执行同步代码块,很快就处理完了。再来一个线程尝试一两次也拿到了锁,再去执行,不会让一个线程等待很久。

重量级锁

如果到了重量级锁,那就没啥说的了,如果有线程持有锁,其他想拿锁的就挂起,等待锁释放后被依次唤醒

锁粗化&锁消除

锁粗化/锁膨胀

锁膨胀是编译Java文件的时候,JIT帮我们做的优化,它会减少锁的获取和释放次数。 比如:

while(){
   synchronized(){
      // 多次的获取和释放,成本太高,会被优化为下面这种
   }
}
synchronized(){
   while(){
       //  拿到锁后执行循环,只加锁和释放一次
   }
}

锁消除

锁消除则是在一个加锁的同步代码块中,没有任何共享资源,也不存在锁竞争的情况,JIT编译时,就直接将锁的指令优化掉。 比如

synchronized(){
   int a = 1;
   a++;
   //操作局部变量的逻辑
}

到此这篇关于深入详解Java中synchronized锁升级的套路的文章就介绍到这了,更多相关Java synchronized锁升级内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java编程学习的几个典型实例详解

    Java编程学习的几个典型实例详解

    这篇文章主要给大家介绍了Java编程学习的几个典型实例,其中包括模拟酒店房间管理系统、螺旋矩阵 例或者百鸡问题的变形等经典实例,具体来一起看详细内容吧,需要的朋友可以参考学习。
    2017-02-02
  • 基于java的opencv开发过程详解

    基于java的opencv开发过程详解

    这篇文章主要介绍了基于java的opencv开发过程详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • springboot项目不同环境的配置读取方式

    springboot项目不同环境的配置读取方式

    SpringBoot支持application.properties、application.yml、application.yaml三种配置文件类型,可同时存在并合并配置,配置文件的读取优先级为:application.properties > application.yml > application.yaml,不同位置的相同类型配置文件
    2024-11-11
  • 关于Java实体类Serializable序列化接口的作用和必要性解析

    关于Java实体类Serializable序列化接口的作用和必要性解析

    序列化是将对象状态转化为可保持或者传输的格式过程,与序列化相反的是反序列化,完成序列化和反序列化,可以存储或传输数据,一般情况下,在定义实体类时会使用Serializable,需要的朋友可以参考下
    2023-05-05
  • Spring为singleton bean注入prototype bean

    Spring为singleton bean注入prototype bean

    这篇文章主要介绍了Spring为singleton bean注入prototype bean,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-07-07
  • java如何对map进行排序详解(map集合的使用)

    java如何对map进行排序详解(map集合的使用)

    这篇文章主要介绍了java如何对map进行排序,java map集合的使用详解,大家可以参考使用
    2013-12-12
  • Spring Boot 3 整合 MinIO 实现分布式文件存储的全过程

    Spring Boot 3 整合 MinIO 实现分布式文件存储的全过程

    本文介绍了如何使用SpringBoot3和MinIO实现分布式文件存储,通过MinIO的分布式对象存储系统,可以解决传统单机文件存储方案在面对大规模数据和高并发访问时的不足,文章详细讲解了MinIO的安装、配置和使用,感兴趣的朋友一起看看吧
    2025-03-03
  • Java中的Caffeine加载与驱逐策略详解

    Java中的Caffeine加载与驱逐策略详解

    这篇文章主要介绍了Java中的Caffeine加载与驱逐策略详解,Caffeine是基于Java 8的高性能缓存库,可提供接近最佳的命中率,Caffeine与ConcurrentMap相应,但是不完全相同,本文主要介绍Caffeine,需要的朋友可以参考下
    2023-10-10
  • 自定义类加载器以及打破双亲委派模型解析

    自定义类加载器以及打破双亲委派模型解析

    这篇文章主要介绍了自定义类加载器以及打破双亲委派模型解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • 使用java的HttpClient实现多线程并发

    使用java的HttpClient实现多线程并发

    这篇文章主要介绍了使用java的HttpClient实现多线程并发的相关资料,需要的朋友可以参考下
    2016-09-09

最新评论