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;从而提…

前端食堂技术周刊第 107 期:技术博客节、Deno Cron、FEDAY、XState v5、Electron 2023 生态系统回顾

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;烤椰拿铁 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 大家好&#xff0c;我是童欧巴。欢迎来到前端食堂技术周刊&#xff0c;我们先来看下…

like concat()函数

mybatis中为了防止sql注入&#xff0c;使用like语句时并不是直接使用&#xff0c;而是使用concat函数 <if test"goodName ! null and goodName ! "> and good_name like concat(%, #{goodName}, %)</if> concat()函数 1、功能&#xff1a;将多个字符串…

【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 …

AI:LangChain

LangChain是一个开源的框架&#xff0c;旨在使开发人员能够轻松构建使用大型语言模型&#xff08;LLMs&#xff09;的应用程序。它提供了一种方式&#xff0c;通过这种方法&#xff0c;开发者可以利用像OpenAI的GPT-3或GPT-3.5这样的模型&#xff0c;以及Hugging Face提供的其他…

UVC debug 工具

v4l2-ctl media-ctl v4l2-ctl和media-ctl是Linux系统中用于配置和控制摄像头的命令行工具。 v4l2-ctl&#xff08;Video for Linux Control&#xff09;是一个用于配置和控制摄像头的工具。它允许用户查看设备的当前状态、设置视频格式、调整图像属性&#xff08;如亮度、对比…

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;或者用怀疑、…

【1day】蓝凌OA 系统custom.jsp 接口远程命令执行漏洞学习

注:该文章来自作者日常学习笔记,请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与作者无关。 目录 一、漏洞概述 二、影响版本 三、资产测绘 四、漏洞复现

梦回吹角连营(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…

【1day】蓝凌OA 系统datajson.js接口远程命令执行漏洞学习

注:该文章来自作者日常学习笔记,请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与作者无关。 目录 一、漏洞概述 二、影响版本 三、资产测绘 四、漏洞复现

要求CHATGPT高质量回答的艺术:提示工程技术的完整指南

要求CHATGPT高质量回答的艺术&#xff1a;提示工程技术的完整指南 第一章&#xff1a;什么是提示工程&#xff1f; 提示工程是创建提示或询问或给出指令的过程&#xff0c;这些指令指导像ChatGPT这样的语言模型的输出。 它允许用户控制模型的输出并生成适合他们特定需求的文…

十三、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…