【JavaEE初阶系列】——synchronized原理及优化(偏向锁,轻量级锁,自旋锁,锁消除,锁粗化)

news2024/11/27 22:26:32

目录

🚩synchronized锁特性详细解说

🚩加锁工作过程(锁升级)

🎈偏向锁

🎈轻量级锁(自适应的自旋锁)

🎈 重量级锁 

🚩其他的优化操作

🎈锁消除

🎈锁粗化

🎈相关面试题


结合上上一篇的锁策略,我们可以了解到锁的特性:

🚩synchronized锁特性

  • 一、开始时是乐观锁,如果锁冲突频繁,就转换为悲观锁。
  • 就比如我去找导员有事,我害怕她不在,白跑一趟,所以我先发信息问问导员在不在办公室[这就相当于加锁],如果得到肯定的回复那么就去,得到否定的回复,那么就不去,等下次他在了,我再去问——这属于悲观锁。
  • 还有一种就是我直接去,不发信息问导员在不在办公室了,如果他在正好,不在就相当于白跑了——这属于乐观锁。

悲观锁和乐观锁其实本质还是哪种方式做的多与少区别。

  • synchronized开始是乐观锁,最后发现锁竞争比较频繁,会自动切换成悲观锁。(如果我之后去的每一次导员都不在,那么之后再去的话我就会先发信息问问导员在不在,以免我又白跑了一趟)。
  • 二、开始时是轻量级锁,如果锁被持续的时间较长,就转换成重量级锁

轻量级锁是纯用户态的加锁解锁逻辑,重量级锁是系统内核态的加锁解锁逻辑。我们之前举例,我去银行取钱,必须得要身份复印件,此时我没有身份证复印件,银行内有个打印机,可以打印,或者去找柜员去打印。我自己去打印机自助打印,打印完就直接交给柜员了,但是如果我交给柜员去打印,我们不清楚柜员中间是否会进行别的操作,可能等的时间长又或者会做其他事情,所以我自己去打印机打印这属于纯用户态即轻量级锁,去找柜员打印则是系统内核态即重量级锁。如果打印机等待打印的时间太长了,我就直接找柜员去给我打印了,此时轻量级锁就转换成了重量级锁。

这两种锁是站在结果的角度看待最终加锁解锁消耗的时间是多还是少,和乐观锁与悲观锁并不一样通常情况下乐观锁比较轻量,悲观锁比较重量,但是也并不绝对。

  • 三、实现轻量级锁的时候大概率用到的自旋锁策略

自旋锁:相当于是"忙等"的状态,大量消耗的CPU资源,反复询问当前锁是否就绪。

我们可以这样想,在我们自己去打印机打印的时候,排队的人很多,一直在等着去打印,这时候挺消耗人精力的,所以我们就一直等等,并且还会时不时看看前面还有多少人,看前面人少了就等待着去打印了。所以我们在实现轻量级锁的时候,大概率需要忙等。

如果获取锁失败 , 立即再尝试获取锁 , 无限循环 , 直到获取到锁为止 . 第一次获取锁失败 , 第二次的尝试会在极短的时间内到来. 一旦锁被其他线程释放, 就能第一时间获取到锁 .

  • 是一种不公平锁

非公平锁:多个线程同时竞争一把锁,有一个线程是比较晚来的,却比其他先来的线程先拿到锁

synchronized锁的获取是非公平的。所谓非公平锁,指的是当多个线程同时请求锁时,锁的获取是随机的,没有任何公平性可言。这意味着,即使某个线程已经等待了很长时间,也不能保证它会在其他线程之前获取到锁。

为什么synchronized是非公平锁呢?这主要是由于synchronized的实现方式决定的。在Java中,synchronized是基于监视器锁(monitor)实现的。每个对象都有一个与之关联的监视器锁,当一个线程进入synchronized代码块时,它会尝试获取对象的监视器锁,如果锁已经被其他线程持有,则该线程会进入阻塞状态,直到锁被释放。

   在非公平锁的情况下,当一个线程释放锁时,JVM并不会保证下一个获取锁的线程是等待时间最长的线程,而是随机选择一个等待的线程来获取锁。这样做的好处是可以减少线程切换的开销,提高系统的吞吐量。但同时也可能导致某些线程长时间等待,造成不公平性。

  • 是一种可重入锁
  • 可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁

之前我们在定义死锁的时候,我们说 一个线程,连续针对一把锁,加锁两次,不会出现死锁 这就是可重入锁。如果出现死锁的现象,那就是不可重入锁。

可重入锁也叫递归锁。

  •  不是读写锁

多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。

而普通的锁就如synchronized锁不是读写锁,不管是读锁和读锁之间,读锁和写锁之间,写锁和写锁之间,都是会造成阻塞等待,而读写锁,只有在数据的写入方互相之间以及和读者之间都需要互斥,其余的在读取方之间不会产生线程安全问题,所以我们在频繁读,不频繁写的场景下,读写锁是很优化的,减少了锁之间的冲突。


🚩加锁工作过程(锁升级)

JVM synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。会根据情况,进行依次升级。


🎈偏向锁

偏向锁其实不是真正的加锁,只是给对象头中做一个 "偏向锁的标记", 记录这个锁属于哪个线程的,如果后续没有其他线程来竞争该锁,那么就不用进行同步操作了(避免了加锁解锁的消耗)

如果后续有其他线程来竞争该锁,(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别 当前申请锁的线程是不是之前记录的线程),那就取消原来偏向锁的状态,进入一般的轻量级锁状态。

举个栗子理解偏向锁
假设男主是一个锁,女主是一个线程,如果只有一个线程来使用这个锁,那么男主女主即使不领证不结婚(避免高成本操作),也可以继续幸福下去。(我们前面也说到了,只有和对方在一起了成为情侣了那才是真正的加锁,但是现在他们并没有领证也没有结婚,只是打着有情侣之实,并没有情侣之名的操作,所以就没有真正的加锁,这只是偏向锁。)
但是女配出现了,也尝试竞争男主,此时不管领证结婚这个操作成本多高,女主也势必要和这个男生结婚领证了。让女配死心。

我们想到之前说到的

  • 线程池,是优化了"找下一任"的概率 
  • 偏向锁,是优化了"分手"的概率 (一旦有锁冲突,我可以直接丢弃,因为我并没有给线程加锁,所以我直接舍弃就行)

偏向锁的核心:

和我们之前所说的"懒汉模式"的另一种体现,需要的时候就再加锁,能不加锁就不加锁。加锁意味着开销。(懒汉模式就是非必要不创建对象)


🎈轻量级锁(自适应的自旋锁)

随着其他线程进行竞争,偏向锁状态被消除,进入轻量级状态(自适应的自旋锁).

一旦受到了锁竞争,那么就立刻和确认关系(确认关系就是加锁了)。这才是真正的加锁。

此处的轻量级锁就是通过 CAS 来实现.
  • 通过 CAS 检查并更新一块内存 (比如 null => 该线程引用)
  • 如果更新成功, 则认为加锁成功
  • 如果更新失败, 则认为锁被占用, 继续自旋式的等待(并不放弃 CPU).

自旋操作是一直让 CPU 空转, 比较浪费 CPU 资源.
因此此处的自旋不会一直持续进行, 而是达到一定的时间/重试次数, 就不再自旋了.
也就是所谓的 "自适应"。

🎈 重量级锁 

如果竞争进一步激烈 , 自旋不能快速获取到锁状态 , 就会膨胀为重量级锁
此处的重量级锁就是指用到内核提供的 mutex .(也就是上面的如果排队的人打印的太多了,此时我们就需要找柜员进行操作也就是从用户态转换成内核态)
  • 执行加锁操作, 先进入内核态.
  • 在内核态判定当前锁是否已经被占用
  • 如果该锁没有占用, 则加锁成功, 并切换回用户态.
  • 如果该锁被占用, 则加锁失败. 此时线程进入锁的等待队列, 挂起. 等待被操作系统唤醒.
  • 经历了一系列的沧海桑田, 这个锁被其他线程释放了, 操作系统也想起了这个挂起的线程, 于是唤醒 这个线程, 尝试重新获取锁.
转换成内核态的时候,我们就要看内核态是否有没有被人占用,如果被人占用,依旧阻塞等待,如果没有,那么就加锁成功,并切换成用户态进行操作一些事情。被人占用后,阻塞等待,等到其他线程释放了后,就唤醒该锁,并重新获取当前锁。

按照这样的加锁工作过程,这样就可以既能保证效率,又能够保证线程安全。

保证效率也就是我上述所说的,一旦遇到锁冲突,那么就立即加锁(轻量级锁)。


🚩其他的优化操作

🎈锁消除

也是一种编译器优化的手段,编译器会自动针对你当前写的加锁代码,做出判定,如果编译器觉得这个场景,不需要加锁,此时就会把你写的synchronized给优化。(编译器只会在自己非常有把握的时候,才会进行锁消除)

我们再javase中学到,StringBuilder和StringBuffer,我们当时说StringBuffer是安全的,StringBuilder是不安全的,因为StringBuffer是带有synchronized的,而StringBuilder是不带有synchronized的。

StringBuffer sb = new StringBuffer();
        sb.append("a");
        sb.append("b");
        sb.append("c");
        sb.append("d");
此时每个 append 的调用都会涉及加锁和解锁. 但如果只是在单线程中执行这个代码, 那么这些加
锁解锁操作是没有必要的, 白白浪费了一些资源开销.

(不是说写了synchronized就一定是安全的,因为再单例模式中,if和new必须同时加锁,因为俩者不能再多线程中实现,会造成线程不安全问题)


🎈锁粗化

一段逻辑中如果出现多次加锁解锁 , 编译器 + JVM 会自动进行锁的粗化 .
锁的粒度: 粗和细
synchronized里头,代码越多,就认为锁的粒度越粗,代码越少,锁的粒度越细。
粒度细的时候,能够并发执行的逻辑更多,更有利于充分利用多核cpu资源,但是,如果粒度细的锁,被反复进行加锁解锁,可能实际效果还不如粒度粗的锁(设计反复的锁竞争)。
举个例子:
滑稽老哥当了领导, 给下属交代工作任务:
方式一:
打电话, 交代任务1, 挂电话.
打电话, 交代任务2, 挂电话.
打电话, 交代任务3, 挂电话.
方式二:
打电话, 交代任务1, 任务2, 任务3, 挂电话.
显然, 方式二是更高效的方案
所以在一段逻辑中多次进行加锁减锁,编译器就会自动的粗化代码,让只加一次锁完成后,就直接解锁。更加高效的完成任务。

可以看到 , synchronized 的策略比较复杂的 , 在背后做了很多事情 , 目的为了让程序猿哪怕啥都不懂 ,也不至于写出特别慢的程序.

🎈相关面试题

1) 什么是偏向锁?

偏向锁不是真正意义上的加锁,而只是在锁的对象头中记录一个标记(记录该锁所属的线程)。如果没有其他锁冲突的话 ,就不会执行真正的加锁操作,从而降低程序开销,一旦真的涉及到其他线程竞争,再取消锁偏向状态,进入轻量级状态。


我所有的野心,就是立刻出发。

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

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

相关文章

【AcWing】蓝桥杯集训每日一题Day10|递归|暴力|数学归纳法|1360.有序分数(C++)

1360.有序分数 1360. 有序分数 - AcWing题库难度:简单时/空限制:1s / 64MB总通过数:4128总尝试数:6630来源:usaco training 2.1算法标签枚举排序最大公约数递归Stern-Brocot Tree 题目内容 给定一个整数 N&#xff0…

Linux利用Jenkins部署SpringBoot项目保姆级教程

在当今快速发展的软件开发领域,持续集成和持续部署(CI/CD)已经成为提升开发效率、缩短产品上市时间的关键实践。Linux系统以其稳定性和开源友好性,成为众多开发者和企业的首选平台。而Spring Boot,作为一个轻量级的Jav…

【Linux入门】Linux简史

Linux 是什么?Linux 是一款叫做操作系统的软件。 操作系统这款软件有什么样的意义呢?简单来说,比如有顾客买了一台笔记本电脑,这台笔记本电脑由电脑硬件组成,在这堆硬件上一定搭载了一款操作系统。正因为操作系统存在&…

QT-自定义参数设计框架软件

QT-自定义参数设计框架软件 前言一、演示效果二、使用步骤1.应用进行参数注册2.数据库操作单例对象3.参数操作单例对象 三、下载链接 前言 常用本地数据参数通常使用的是xml等文本的格式,进行本地的数据参数的存储。这种参数的保存方式有个致命的一点,就…

linux监控命令全

1.1 top 1.1.1 命令说明 Top 命令能够实时监控系统的运行状态,并且可以按照cpu、内存和执行时间进行排序 1.1.2 用法 top -hv | -bcisSHM -d delay -n iterations [-u user | -U user] -p pid [,pid ...] 1.1.3 参数说明 命令行启动参数: -b : 批次…

从入门到实战:vue3路由知识点

本人在B站上关于vue3的尚硅谷的课程,以下是整理一些笔记。 1.两个知识点 1.路由组件通常存放在pages 或 views文件夹,一般组件通常存放在components文件夹。 组件可以分为: 1. 一般组件:亲手写标签出来的 2. 路由组件&#…

【检索增强】Retrieval-Augmented Generation for Large Language Models:A Survey

本文简介 1、对最先进水平RAG进行了全面和系统的回顾,通过包括朴素RAG、高级RAG和模块化RAG在内的范式描述了它的演变。这篇综述的背景下,更广泛的范围内的法学硕士研究RAG的景观。 2、确定并讨论了RAG过程中不可或缺的核心技术,特别关注“…

成都直播基地 天府新区产业园能获得哪些政府支持

为了推动成都直播产业的快速发展,政府出台了一系列政策措施,为成都直播基地提供了全方位的支持。本篇文章将为您具体解析入驻成都直播基地 天府新区产业园 天府锋巢直播产业基地都能获得哪些政府支持。 首先,天府新区作为成都市的重要发展区…

【亚马逊云科技】使用 Vscode Amazon-Q 完成 GUI 界面粉笔脚本开发

前言 亚马逊云科技- Q ,可以快速获得紧迫问题的相关答案,解决问题,生成内容。当与 Q 聊天时,它会提供即时的相关信息和建议,以帮助简化任务、加快决策速度,并帮助激发工作中的创造力和创新。本次我们通过完…

捷途山海T2正式开启预售,新能源方盒子SUV仅售18.49万起

4月2日,捷途汽车宣布,定位为“旅行越野超混SUV”的山海T2正式开启预售。新车共计将推出3款不同配置车型,预售价格区间为18.49万-21.69万元。同时,预售期间捷途官方还将为用户推出7重预售礼。 山海T2是捷途山海系列第二款产品&…

idea使用docker将Java项目生成镜像并使用

1:开启docker 远程访问 使用 vim 编辑docker服务配置文件 vim /lib/systemd/system/docker.service [Service] Typenotify # the default is not to use systemd for cgroups because the delegate issues still # exists and systemd currently does not suppor…

【Python从入门到进阶】52、CrawlSpider链接提取器的使用

接上篇《51、电影天堂网站多页面下载实战》 上一篇我们采用Scrapy框架多页面下载的模式来实现电影天堂网站的电影标题及图片抓取。本篇我们来学习基于规则进行跟踪和自动爬取网页数据的“特殊爬虫”CrawlSpider。 一、什么是CrawlSpider? 1、CrawlSpider的概念 Cr…

互联网轻量级框架整合之JavaEE基础I

不得不解释得几个概念 JavaEE SUN公司提出来的企业版Java开发中间件,主要用于企业级互联网系统的框架搭建,同时因为Java语言优质的平台无关性、可移植性、健壮性、支持多线程和安全性等优势,其迅速成为构建企业互联网平台的主流技术&#x…

【技巧】Leetcode 287. 寻找重复数【中等】

寻找重复数 给定一个包含 n 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。 假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。 你设计的解决方案必须 不修改 数组…

搜维尔科技:Manus Prime 3 Mocap数据手套,体验极致的每指触觉!

完全适用于VR虚拟现实场景 特斯拉也在使用的量子数据 Tesla 目前正在使用 MANUS Quantum Metagloves创建一个数据集,帮助他们训练 Tesla 机器人。 量子数据训练QUANTUM AI 我们以类似的方式使用 Quantum Metagloves 来生成一流的手指跟踪数据集,并将其…

yolov5目标检测可视化界面pyside6源码(无登录版)

一、软件简介: 这是基于yolov5-7.0目标检测实现的的可视化目标检测源码 本套项目没有用户登录的功能,如需用户登录版,看另一篇文章:yolov5pyside6登录用户管理目标检测可视化源码_yolov5用户登入功能-CSDN博客 ①程序中图片和图标…

护眼台灯怎么选看哪些指标?护眼灯十大品牌推荐

在追求高效工作与学习的同时,如何保护视力健康,避免长时间用眼带来的疲劳与伤害,已成为现代人关注的焦点。护眼台灯作为提升用眼环境的重要工具,其选择显得尤为关键。那么,面对市面上琳琅满目的护眼台灯产品&#xff0…

分治dp,LeetCode 894. 所有可能的真二叉树

目录 一、题目 1、题目描述 2、接口描述 ​cpp python3 3、原题链接 二、解题报告 1、思路分析 F1 回溯 F2 动态规划 2、复杂度 3、代码详解 ​分治 cpp python3 dp cpp python3 一、题目 1、题目描述 给你一个整数 n ,请你找出所有可能含 n 个节…

数学矩阵(详解)

矩阵乘法 知阵乘法是《线性代数》中的基础内容,但在考察数学的算法题中也会出现。 本节我们学习基础的矩阵乘法规则。 每个矩阵会有一个行数和一个列数,只有当相乘的两个矩阵的左矩阵的列数等于右矩阵的行数 时,才能相乘,否则不允…

蓝桥杯单片机速成2-动态数码管数码管显示

一、原理图 段选给1是选中 ,该数码管是共阳极的数码管,位选输入0才会电亮一位 二、代码分析 /************* 本地常量声明 **************/ u8 code t_display[]{ //标准字库 // 0 1 2 3 4 5 6 7 8…