synchronized底层原理(二)

书接上文

文章目录

    • 1. 锁升级原理
    • 2. Synchronized锁优化
      • 1. 偏向锁批量重偏向&批量撤销
      • 2. 自旋优化
      • 3. 锁粗化
      • 4. 锁消除

1. 锁升级原理

前面介绍了对象的几种加锁状态,分别是无锁、偏向锁、轻量级锁和重量级锁。有下面几个关键点:

  • 当开启JVM偏向延迟时对象初始状态为无锁,若加锁后则变为轻量级锁,轻量级锁在发生锁竞争时,竞争锁的线程会通过一次CAS自旋判断能不能获取锁,如果在这个期间另一个线程释放了锁,那么锁还是轻量级锁,否则膨胀为重量级锁。(轻量级锁和重量级锁释放锁后就会变成无锁状态,再次加锁还是会相应的变成轻量级锁和重量级锁)
  • 当关闭JVM延迟偏向时,对象初始创建为偏向状态,初始默认为不偏向任何线程,加锁后偏向指定加锁线程,如果发生偏向撤销(如调用hashcode)的情况,若对象没有被锁时偏向锁会变为无锁状态,若锁定了会变成轻量级锁,若当前对象锁定,且在同步代码块中调用hashcode方法或者wait方法会直接升级为重量级锁,注意偏向锁释放后对象不会变为无锁状态,还是会保持偏向状态。

在这里插入图片描述

从图中可以发现无锁状态也可以直接膨胀为重量级锁状态,这里解释一下,首先我们需要了解一下无锁状态是怎么变为轻量级锁状态的。

在这里插入图片描述
主要分为三个步骤:

  • 首先复制mark word到displaced word(注意只有该线程第一次加轻量级锁的时候会设置displaced word,后续发生锁重入时都会设置为null)
  • CAS将对象mark word的信息替换为指向现场操作数栈顶层的锁记录
  • 修改mark word锁记录为00
  • 将栈帧中的锁记录obj指向锁定的对象

当字节码解释器执行monitorenter字节码轻度锁住一个对象时,就会在获取锁的线程,显示或者隐式分配一个lockword。若在上面加轻量级锁时发生了激烈竞争,轻量级锁会直接膨胀为重量级锁。

2. Synchronized锁优化

1. 偏向锁批量重偏向&批量撤销

从偏向锁的加锁解锁过程中可看出,当只有一个线程反复进入同步块时,偏向锁带来的性能开销基本可以忽略,但是当有其他线程尝试获得锁时,就需要等到safe point(安全点)时,再将偏向锁撤销为无锁状态或升级为轻量级,会消耗一定的性能,所以在多线程竞争频繁的情况下,偏向锁不仅不能提高性能,还会导致性能下降。于是,就有了批量重偏向与批量撤销的机制。

批量重偏向:随着时间的推移,原先获取偏向锁的线程可能会不再访问锁。为了防止这种情况下过多的线程都尝试争夺锁,Java引入了批量重偏向机制。批量重偏向是指当某个线程获取锁的时候,JVM会检查此锁的偏向状态,如果发现有一定数量(默认为20次)的线程都不再访问这个锁,那么JVM会认为这个锁不再是偏向锁,而是要进行批量重偏向,重新选取一个线程来获得锁,并更新偏向锁的线程ID。

批量撤销:是指当有很多线程都尝试获取某个锁时,JVM会判断当前的锁是否适合做为偏向锁,如果不适合,就会取消偏向状态,将锁升级为轻量级锁或重量级锁。这样可以防止偏向锁机制在高竞争的情况下带来额外的性能损失。

总结原理就是:以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。每个class对象会有一个对应的epoch字段,每个处于偏向锁状态对象的Mark Word中也有该字段,其初始值为创建该对象时class中的epoch的值。每次发生批量重偏向时,就将该值+1,同时遍历JVM中所有线程的栈,找到该class所有正处于加锁状态的偏向锁,将其epoch字段改为新值。下次获得锁时,发现当前对象的epoch值和class的epoch不相等,那就算当前已经偏向了其他线程,也不会执行撤销操作,而是直接通过CAS操作将其Mark Word的Thread Id 改成当前线程Id。当达到重偏向阈值(默认20)后,假设该class计数器继续增长,当其达到批量撤销的阈值后 (默认40),JVM就认为该class的使用场景存在多线程竞争,会标记该class为不可偏向,之后, 对于该class的锁,直接走轻量级锁的逻辑。批量重偏向(bulk rebias)机制是为了解决:一个线程创建了大量对象并执行了初始的同步操作,后来另一个线程也来将这些对象作为锁对象进行操作,这样会导致大量的偏向锁撤销操作。 批量撤销(bulk revoke)机制是为了解决:在明显多线程竞争剧烈的场景下使用偏向锁是不合适的。下面代码演示一下:
在这里插入图片描述

public class Main {public static void main(String[] args) throws InterruptedException {//偏向锁延迟Thread.sleep(5000);//用来存放锁对象List<Object> jack=new ArrayList<>();new Thread(()->{for (int i = 0; i < 50; i++) {//创建锁对象并添加的集合中Object obj=new Object();//保持可见性synchronized (obj){jack.add(obj);}}try {//保持线程t1存活Thread.sleep(100000);}catch (Exception e){e.printStackTrace();}},"t1").start();//保证对象创建完成Thread.sleep(3000);System.out.println("对象的初始对象头:"+ClassLayout.parseInstance(jack.get(19)).toPrintable());new Thread(()->{for (int i = 0; i < 40; i++) {Object obj=jack.get(i);synchronized (obj){if(i>=15 && i<=21||i>=38){System.out.println("线程t2第"+(i+1)+"次加锁:" + ClassLayout.parseInstance(obj).toPrintable());}}if(i==17 || i==19){System.out.println("线程t2第"+(i+1)+"次释放锁:" + ClassLayout.parseInstance(obj).toPrintable());}}},"t2").start();}
}

我们来分析一下输出结果:
在这里插入图片描述

首先初始状态对象偏向线程t1

在这里插入图片描述

16次加锁时为轻量级锁

在这里插入图片描述

第17次加锁时为轻量级锁,17次解锁为无锁状态

在这里插入图片描述

18次加锁此时就是发生了重偏向变回了偏向锁,后面的结果都会是所有对象的偏向锁偏向了新的线程(不知道为什么不是阈值20)

下面再来测试批量撤销
当撤销偏向锁阈值超过 40 次后,jvm 会认为不该偏向,于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。 注意:时间-XX:BiasedLockingDecayTime=25000ms范围内没有达到40次,撤销次数清为0, 重新计时

在这里插入图片描述

发现所有的50次都在做偏向锁撤销

在这里插入图片描述

新创建的对象直接变为无锁状态

上面的现象可以总结为三点:

  1. 批量重偏向和批量撤销是针对类的优化,和对象无关。
  2. 偏向锁重偏向一次之后不可再次重偏向。
  3. 当某个类已经触发批量撤销机制后,JVM会默认当前类产生了严重的问题,剥夺了该类新实例对象使用偏向锁的权利

2. 自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  • 在 Java 6 之后自旋是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,比较智能。
  • Java 7 之后不能控制是否开启自旋功能,使用-XX:PreBlockSpin参数来设置自旋锁等待次数

注意:自旋的目的是为了减少线程挂起的次数,尽量避免直接挂起线程(挂起操作涉及系统调用,存在用户态和内核态切换,这才是重量级锁最大的开销)

3. 锁粗化

假设一系列的连续操作都会对同一个对象反复加锁及解锁,甚至加锁操作是出现在循环体中的,即使没有出现线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。如果JVM检测到有一连串零碎的操作都是对同一对象的加锁,将会扩大加锁同步的范围(即锁粗化)到整个操作序列的外部。

StringBuffer buffer=new StringBuffer(); /**
*锁粗化
*/
public void append(){buffer.append("aaa").append(" bbb").append(" ccc");
}

append源码如下:

 public synchronized StringBuffer append(CharSequence s) {toStringCache = null;super.append(s);return this;}

可以发现它是同步方法,所以向上面那个append方法连续加aaa,bbb和ccc三个字符串,是需要多长加锁解锁的。如果JVM检测到有一连串的对同一个对象加锁和解锁的操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次 append方法时进行加锁,最后一次append方法结束后进行解锁。

4. 锁消除

锁消除即删除不必要的加锁操作。锁消除是Java虚拟机在JIT编译期间,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过锁消除,可以节省毫无意义的请求锁时间。

public class LockEliminationTest{/***锁消除* ‐XX:+EliminateLocks 开启锁消除(jdk8默认开启)* ‐XX:‐EliminateLocks 关闭锁消除* @param str1* @param str2*/public void append(String str1, String str2) {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append(str1).append(str2);}public static void main(String[] args) throws InterruptedException {LockEliminationTest demo = new LockEliminationTest();long start = System.currentTimeMillis();for (int i = 0; i < 100000000; i++) {demo.append("aaa", "bbb")
}
long end = System.currentTimeMillis(); System.out.println("执行时间:" + (end ‐ start) + " ms"); }
}

StringBuffer的append是个同步方法,但是append方法中的 StringBuffer 属于一个局部变量,不可能从该方法中逃逸出去,因此其实这过程是线程安全的,可以将锁消除。(这里就涉及一个逃逸分析(这里是JIT优化的内容)的概念)

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

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

相关文章

什么是美颜sdk?美颜sdk对比评测、技术评估

为了满足用户对于更美好画面的需求&#xff0c;各种美颜sdk应运而生。本文将深入探讨美颜sdk的概念&#xff0c;进行对比评测&#xff0c;并对其技术进行综合评估。 一、什么是美颜sdk&#xff1f; 美颜sdk使开发者们可以方便地在自己的应用中集成美颜功能&#xff0c;从而提…

【5】PyQt按钮

QPushButton 常见的按钮实现类包括:QPushButton、QRadioButton和QCheckBox QPushButton是最普通的按钮控件&#xff0c;可以响应一些用户的事件 from PyQt5.QtWidgets import QApplication, QWidget, QPushButton import sysdef func():print("按下按钮啦&#xff0c;火…

C语言每日一题(46)整数转罗马数字

力扣网12 整数转罗马数字 题目描述 罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D …

Isaac Sim教程06 OmniGraph图编程

Isaac Sim OmniGraph图编程 版权信息 Copyright 2023 Herman YeAuromix. All rights reserved.This course and all of its associated content, including but not limited to text, images, videos, and any other materials, are protected by copyright law. The autho…

Spring容器启动过程中的自定义操作插口汇总

目录标题 PostConstruct注解EventListener方式InitializingBean的afterPropertiesSet方法实现ApplicationRunner接口重写run方法实现AplicationContextAware接口重写setApplicationContext实现ServletContextListener接口contextInitialized方法实现ServletContextAware接口set…

7个简单技巧,让你从容应对压力面试!

01-什么是压力面试&#xff1f; 压力面试是指有意制造紧张&#xff0c;以了解求职者将如何面对工作压力的一种面试形式。 事实上&#xff0c;压力面试不是单独存在的一类面试&#xff0c;往往是穿插在面试过程中。 面试人通过提出不礼貌、冒犯的问题&#xff0c;或者用怀疑、…

梦回吹角连营(2)(快速幂快乘)

Description 给定f(n)(a1)*n^a(a2)*n^(a1)...b*n^(b-1) 求f(n)%10000000033 Input 输入一个正整数T(T<10),表示有T组数据&#xff0c;每组数据包括三个整数a,b,n (0<n<10^9,1<a < b-1<10^20) Output 输出 f(n)%10000000033 的结果 Sample Input 1 1 2…

十三、FreeRTOS之FreeRTOS时间管理

本节主要介绍以下内容&#xff1a; 1&#xff0c;延时函数介绍&#xff08;了解&#xff09; 2&#xff0c;延时函数解析&#xff08;熟悉&#xff09; 3&#xff0c;延时函数演示实验&#xff08;掌握&#xff09; 4&#xff0c;课堂总结&#xff08;掌握&#xff09; 一、…

最新测试开发招聘信息汇总,内含社招和实习生岗位~

1 科大讯飞 [武汉/合肥/西安] 科大讯飞&#xff08;教育事业部&#xff09; - 移动、服务端高级测试开发工程师 一、移动方向高级测试开发工程师 岗位职责&#xff1a; 1.负责教育 BG 中 APP/SDK/软硬一体等产品类型的专项测试工作&#xff0c;包括专项测试方案设计、自动化测…

通过时间交织技术扩展ADC采样速率的简要原理

前言 数据采集是将自然界中存在的模拟信号通过模数转换器&#xff08;ADC&#xff09;转换成数字信号&#xff0c;再对该数字信号进行相应的接收和处理。数据采集系统作为数据采集的手段&#xff0c;在移动通信、图向采集、无线电等领域有重要作用。随着电子信息技术的飞速发展…

【计算机系统基石与Linux进程管理深度解析】

​​​​​​​ 【本节重点】 认识冯诺依曼系统 操作系统概念与定位 深入理解进程概念&#xff0c;了解PCB 学习进程状态&#xff0c;学会创建进程&#xff0c;掌握僵尸进程和孤儿进程&#xff0c;及其形成原因和危害 1.冯诺依曼体系结构 我们常见的计算机&#xff0c;如…

IO / day03 作业

1. 使用文件IO完成对图像的读写操作 代码 #include<myhead.h>int main(int argc, const char *argv[]) {int fd-1;if((fd open("./bird.bmp", O_RDWR)) -1){perror("fopen error");return -1;}//读取该图片的大小&#xff0c;需要将光标向后偏移…

通过pipeline配置sonar自动化实现过程解析

这篇文章主要介绍了通过pipeline配置sonar自动化实现过程解析,文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.sonar配置webhooks&#xff0c; 2.url填写jenkins的地址&#xff1a;http://jenkinsurl/sonarqu…

高低压供配电智能监控系统

高低压供配电智能监控系统是一种综合运用物联网、云计算、大数据和人工智能等技术的智能化监控系统&#xff0c;用于实时监测高低压供配电设备的运行状态和电能质量&#xff0c;及时发现和处理供配电系统中存在的问题&#xff0c;提高供配电系统的安全性和可靠性。依托电易云-智…

LLM之Agent(四)| AgentGPT:一个在浏览器运行的Agent

AgentGPT是一个自主人工智能Agent平台&#xff0c;用户只需要为Agent指定一个名称和目标&#xff0c;就可以在浏览器中链接大型语言模型&#xff08;如GPT-4&#xff09;来创建和部署Agent平台。 PS&#xff1a;目前agentGPT仅支持chatgpt模型&#xff0c;暂时不支持本地llm模…

调查显示 IT 服务事件越来越频繁

事件管理平台提供商 Transposit 对美国 1,000 名 IT 运营、DevOps、站点可靠性工程 (SRE) 和平台工程专业人士进行的一项调查发现&#xff0c;超过三分之二 (67%) 的人发现故障率有所增加过去 12 个月中影响客户的服务事件的频率。 今天在Kubecon CloudNative会议上宣布的调查…

102.套接字-Socket网络编程4(TCP通信流程)

目录 TCP编程流程 套接字函数 1.创建套接字 2.绑定地址 3.监听连接请求 4.接受连接 5. 连接到服务器 6. 发送数据 7. 接收数据 8.关闭套接字 服务器端通信流程 示例代码 客户端通信流程 代码示例 TCP编程流程 TCP是一个面向连接的&#xff0c;安全的&#xff0c;流…

在线测量大尺寸管材的测径仪有哪些?

工业高速发展的背后&#xff0c;离不开与之匹配的高端设备作为科研的支撑。品质检测仪器也在随着现代科技的发展而不断变化&#xff0c;随着科技的进步&#xff0c;各种大口径的管材、管道被生产制造出来&#xff0c;而对其外径尺寸的检测则因口径范围大而使得很少有仪器能进行…

美股电动汽车股票分析:蔚来和Rivian这两只都遭受了重创的股票,哪个更值得投资?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 Rivian(RIVN)和蔚来(NIO)都是目前美股市场上最受关注的电动汽车股票。虽然蔚来在全球最大的电动汽车市场中国扮演着重要角色&#xff0c;但Rivian也击败了很多传统汽车制造商&#xff0c;并成为了第一家在美国推出全电动皮…

MongoDB的学习记录

目录 MongoDB是什么MongoDB为什么存在MongoDB怎么用MongoDB解决什么MongoDB指令开发使用的mongoose模块讲解请看这篇文章 MongoDB是什么 MongoDB是一种开源的、面向文档的NoSQL数据库管理系统。它使用JSON格式来存储数据&#xff0c;具有高可扩展性、高性能、高可用性等特点&a…