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优化的内容)的概念)

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

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

相关文章

外贸电商ERP品牌有哪些

随着现代物流科技的发展进步&#xff0c;外贸电商行业也迎来新的发展阶段&#xff0c;各种经营数据的统计分析工作量较大。同时还有不少商贸企业经营管理工作涉及多货币、多汇率核算、多店铺数据协同、多业务模式等&#xff0c;而想要高效处理这些事务&#xff0c;传统的管理模…

游戏开发增笑-扣扣死-Editor的脚本属性自定义定制-还写的挺详细的,旧版本反而更好

2012年在官方论坛注册的一个号&#xff0c;居然被禁言了&#xff0c;不知道官方现在是什么辣鸡&#xff0c;算了&#xff0c;大人不记狗子过 ”后来提交问题给CEO了&#xff0c;结果CEO百忙之中居然回复了&#xff0c;也是很低调的一个人&#xff0c;毕竟做技术的有什么坏心思呢…

数据结构与算法编程题41

线性表中各结点的检索概率不等时&#xff0c;可用如下策略提高顺序检索的效率&#xff1a; 若找到指定的结点&#xff0c;则将该结点和其前驱结点&#xff08;若存在&#xff09;交换&#xff0c;使得经常被检索 的结点尽量位于表的前端。试设计在顺序结构的线性表上实现上述策…

Linux中项目部署步骤

安装jdk&#xff0c;tomcat 安装步骤 1&#xff0c;将压缩包&#xff0c;拷贝到虚拟机中。 通过工具&#xff0c;将文件直接拖到虚拟机的/home下 2&#xff0c;回到虚拟机中&#xff0c;查看/home下&#xff0c;有两个压缩文件 3&#xff0c;给压缩文件做解压缩操作 tar -z…

vue项目解决计算后浮点数精度问题

1.1 问题描述 计算出的结果本来应该为13.8386&#xff0c;但是这里因为js精度问题&#xff0c;导致后边多了一串的0000001。 1.2 使用场景 求和&#xff0c;每个物品的单价*数量 1.3 解决办法 引入第三方库Decimal 1.4 vue项目中Decimal安装步骤 1.4.1 安装Decimal np…

【Cesium】模型平面裁切

const scene viewer.scene;let tileset; let targetY 400.0; let planeEntities []; let selectedPlane; // 选择的切面 let clippingPlanes; // 切面属性// 当鼠标点击切面时&#xff0c;修改相关属性 const downHandler new Cesium.ScreenSpaceEventHandler(viewer.sce…

Pinia的基础使用

main.ts import { createApp } from "vue"; import { createPinia } from "pinia"; import "./style.css"; import App from "./App.vue"; const pinia createPinia(); createApp(App).use(pinia).mount("#app");1.定义st…

商务助理个人简历10篇

商务助理简历模板下载&#xff08;可在线编辑制作&#xff09;&#xff1a;来幻主简历&#xff0c;做好简历&#xff01; 商务助理简历1&#xff1a; 求职意向 求职类型&#xff1a;全职 意向岗位&#xff1a;国际商务、产品助理 意向城市&#xff1a;广东广州 …

驱动开发--内核添加新功能

Ubuntu下这个文件为开发板ls命令的结果 内核的内容&#xff1a; mm&#xff1a;内存管理 fs&#xff1a;文件系统 net&#xff1a;网络协议栈 drivers&#xff1a;驱动设备 arch与init&#xff1a;跟启动相关 kernel与ipc&#xff1a;任务&#xff0c;进程相关 向内核增…

家用打印机品牌多,种类杂,那么如何挑选最适合的家用打印机

在购买最好的家用打印机时&#xff0c;你可能会寻找足够多功能的打印机来满足每个人的需求。你的家人可能需要复印文件签字&#xff0c;扫描精致的旧照片&#xff0c;或者在接到通知后立即打印长篇文章或报告。良好的扫描功能确保你可以快速高效地将工作数字化&#xff0c;而每…

TCP/IP的体系结构

目录 一、TCP/IP的体系结构 二、TCP/IP四层协议的表示方法举例 三、现在因特网使用的TCP/IP体系结构 四、互联网应用层的客户——服务器方式 一、TCP/IP的体系结构 二、TCP/IP四层协议的表示方法举例 三、现在因特网使用的TCP/IP体系结构 四、互联网应用层的客户——服务器…

校园局域网规划设计

摘 要 随着网络技术的发展&#xff0c;校园网的建设已经进入到一个蓬勃发展的阶段。校园网的建成和使用&#xff0c;对于提高教学和科研的质量、改善教学和科研条件、加快学校的信息化进程&#xff0c;开展多媒体教学与研究以及使教学多出人才、科研多出成果有着十分重要而深远…

香港服务器时间不准,差8小时

解决方案1 1、timedatectl查看系统时间 2、查看系统时区 ls /usr/share/zoneinfo 3、删除当前系统所处时区 rm /etc/localtime 4、创建软链接&#xff0c;以替换当前的时区信息 ln -s /usr/share/zoneinfo/Universal /etc/localtime 解决方案2 手动设置硬件时钟 1、设置系…

MySQL limit导致索引选择(选择的并不是最佳索引)案例分析

mysql limit导致索引选择&#xff08;选择的并不是最佳索引&#xff09;案例分析&#xff1a; 这种情况可能是mysql优化器内部bug造成&#xff1a; bug 触发条件如下: 1.优化器先选择了 where 条件中字段的索引&#xff0c;该索引过滤性较好&#xff1b; 2.SQL 中必须有 orde…

视频处理关键知识

1 引言 视频技术发展到现在已经有100多年的历史&#xff0c;虽然比照相技术历史时间短&#xff0c;但在过去很长一段时间之内都是最重要的媒体。由于互联网在新世纪的崛起&#xff0c;使得传统的媒体技术有了更好的发展平台&#xff0c;应运而生了新的多媒体技术。而多媒体技术…

合理布局CRM系统,提升工作效率

一般来说中小企业试用的CRM系统的销售管理模块主要服务于销售人员&#xff0c;CRM系统通过为销售人员提供一系列销售自动化工具&#xff0c;来简化他们的工作&#xff0c;加速销售周期。那么&#xff0c;中小企业CRM系统如何提高销售效率&#xff1f; 一、通用功能 1、销售管…

人工智能辅助决策中的反常识与反逻辑

在人工智能辅助决策中&#xff0c;直觉与假设、常识与逻辑起着重要的作用。机器学习模型可以通过大量数据训练获得某种机器直觉&#xff0c;从而帮助我们更好地理解和分析数据&#xff0c;我们可以根据已有的数据和知识来推断未知的信息&#xff0c;更加准确地预测和判断结果&a…

外汇天眼:假冒、非法经营成常态?超60家外汇平台被拉黑

近期&#xff0c;全球范围内多个国家的金融监管机构纷纷发出警告&#xff0c;揭露一系列假冒、非法经营的外汇交易平台。比利时金融服务和市场管理局&#xff08;FSMA&#xff09;发现53家外汇平台涉嫌非法运营&#xff0c;而意大利CONSOB和英国金融行为监管局&#xff08;FCA&…

uniapp开发小程序经验记录

uniapp开发小程序的过程中会遇到很多问题&#xff0c;这里记录一下相关工具优化&#xff0c;便于后来者参考。 每次保存代码后&#xff0c;小程序都跳回首页 针对这个问题&#xff0c;常规的做法就是修改pages配置文件&#xff0c;但是这种方式不便于路由参数的设置&#xff…

什么是静态链接?有什么用?

文章目录 Linux下的链接器:ldld的作用 可重定位目标文件可重定位目标文件的结构是怎么样的?1. 文件头&#xff08;File Header&#xff09;&#xff1a;2. 节头表&#xff08;Section Header Table&#xff09;&#xff1a;3. 节区&#xff08;Sections&#xff09;&#xff1…