Synchronized关键字的深入分析

一、引言

在多线程编程中,正确地管理并发是确保程序正确运行的关键。Java提供了多种同步工具,其中synchronized关键字是最基本且最常用的同步机制之一。本文旨在深入解析synchronized的实现原理,探讨其在不同应用场景中的使用,并通过示例让读者更好地理解其工作机制。

二、Synchronized基本概念

2.1 定义和作用

synchronized关键字可以用来修饰方法或者代码块。在方法或代码块被执行时,它能够保证同一时刻只有一个线程执行该段代码。这一特性使得synchronized成为实现临界区(Critical Section)和避免竞态条件(Race Condition)的简便方法。

2.2 使用方法

  • 同步实例方法:锁定当前实例对象
public synchronized void method() {// 同步代码
}
  • 同步静态方法:锁定当前类的Class对象。
public static synchronized void staticMethod() {// 同步代码
}
  • 同步代码块:指定一个特定对象作为锁。
public void method() {synchronized(this) {// 同步代码}
}

2.3 对比其他关键字

volatilefinal相比,synchronized不仅能保证可见性和顺序性,还能保证原子性。volatile仅保证变量的修改可见性和禁止指令重排序,而final关键字则用于声明常量。

三、Synchronized的内部机制

3.1 Java内存模型(JMM)

JMM处理了变量的可见性、原子性问题,为开发者屏蔽了不同CPU的复杂性。它确保一个线程对共享变量的修改,能够被其他线程看到,是通过内存屏障实现的。

3.2 锁的状态

在 JDK 6 中虚拟机团队对锁进行了重要改进,优化了其性能引入了 偏向锁、轻量级锁、适应性自旋、锁消除、锁粗化等实现,其中 锁消除和锁粗化本文不做详细讨论其余内容我们将对其进行逐一探究。

总体上来说锁状态升级流程如下:
在这里插入图片描述

  • 无锁状态
  • 偏向锁:假定锁不会存在竞争,避免了大多数情况下的同步。
  • 轻量级锁:当锁是偏向锁时,被另一个线程访问,会升级为轻量级锁。
  • 重量级锁:多线程竞争激烈时,轻量级锁会升级为重量级锁。

3.3 各种锁的获取流程

偏向锁
流程

当线程访问同步块并获取锁时处理流程如下:

  • 检查 mark word 的线程 id 。
  • 如果为空则设置 CAS 替换当前线程 id。如果替换成功则获取锁成功,如果失败则撤销偏向锁。
  • 如果不为空则检查 线程 id为是否为本线程。如果是则获取锁成功,如果失败则撤销偏向锁。

持有偏向锁的线程以后每次进入这个锁相关的同步块时,只需比对一下 mark word 的线程 id 是否为本线程,如果是则获取锁成功。

如果发生线程竞争发生 2、3 步失败的情况则需要撤销偏向锁。

偏向锁的撤销
  1. 偏向锁的撤销动作必须等待全局安全点
  2. 暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态
  3. 撤销偏向锁恢复到无锁(标志位为 01)或轻量级锁(标志位为 00)的状态
优点

只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。

缺点

如果存在竞争会带来额外的锁撤销操作。

轻量级锁
加锁

多个线程竞争偏向锁导致偏向锁升级为轻量级锁

  1. JVM 在当前线程的栈帧中创建 Lock Reocrd,并将对象头中的 Mark Word 复制到 Lock Reocrd 中。(Displaced Mark Word)
  2. 线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向 Lock Reocrd 的指针。如果成功则获得锁,如果失败则先检查对象的 Mark Word 是否指向当前线程的栈帧如果是则说明已经获取锁,否则说明其它线程竞争锁则膨胀为重量级锁。
解锁
  1. 使用 CAS 操作将 Mark Word 还原
  2. 如果第 1 步执行成功则释放完成
  3. 如果第 1 步执行失败则膨胀为重量级锁。
优点

其性能提升的依据是对于绝大部分的锁在整个生命周期内都是不会存在竞争。在多线程交替执行同步块的情况下,可以避免重量级锁引起的性能消耗。

缺点

在有多线程竞争的情况下轻量级锁增加了额外开销。

自旋锁

自旋是一种获取锁的机制并不是一个锁状态。在膨胀为重量级锁的过程中或重入时会多次尝试自旋获取锁以避免线程唤醒的开销,但是它会占用 CPU 的时间因此如果同步代码块执行时间很短自旋等待的效果就很好,反之则浪费了 CPU 资源。默认情况下自旋次数是 10 次用户可以使用参数 -XX : PreBlockSpin 来更改。那么如何优化来避免此情况发生呢?我们来看适应性自旋。

适应性自旋锁

JDK 6 引入了自适应自旋锁,意味着自旋的次数不在固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果对于某个锁很少自旋成功那么以后有可能省略掉自旋过程以避免资源浪费。有了自适应自旋随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虛拟机就会变得越来越“聪明”了。

优点

竞争的线程不会阻塞挂起,提高了程序响应速度。避免重量级锁引起的性能消耗。

缺点

如果线程始终无法获取锁,自旋消耗 CPU 最终会膨胀为重量级锁。

重量级锁

在重量级锁中没有竞争到锁的对象会 park 被挂起,退出同步块时 unpark 唤醒后续线程。唤醒操作涉及到操作系统调度会有额外的开销。

ObjectMonitor 中包含一个同步队列(由 _cxq_EntryList 组成)一个等待队列( _WaitSet )。

  • 被notify或 notifyAll 唤醒时根据 policy 策略选择加入的队列(policy 默认为 0)
  • 退出同步块时根据 QMode 策略来唤醒下一个线程(QMode 默认为 0)

这里稍微提及一下管程这个概念。synchronized 关键字及 waitnotifynotifyAll 这三个方法都是管程的组成部分。可以说管程就是一把解决并发问题的万能钥匙。有两大核心问题管程都是能够解决的:

  • 互斥:即同一时刻只允许一个线程访问共享资源;
  • 同步:即线程之间如何通信、协作。

synchronizedmonitor锁机制和 JDK 并发包中的 AQS 是很相似的,只不过 AQS 中是一个同步队列多个等待队列。熟悉 AQS 的同学可以拿来做个对比。

3.3 锁升级过程

锁的升级是自动的,以减少锁的开销。例如,从偏向锁到轻量级锁的升级发生在第一个获取偏向锁的线程之外的线程尝试获取这个锁时。

3.4 对象头和锁标记位

Java对象头包含了对象的运行时数据,例如哈希码、GC标记位、锁状态等。这些信息对于锁的管理至关重要。
在这里插入图片描述

附录:队列协作流程图

在这里插入图片描述

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

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

相关文章

(ChatGPT中文、吾爱Al、核桃、WeexAl地址发布页、ai创作、Chat中文)分享好用的ChatGPT

目录 1、ChatGPT 中文 - Chat GPT 2、吾爱AI 3、 核桃 4、WeexAI 地址发布页 5、ai创作

​解析什么是物联网接入网关?-天拓四方

随着物联网技术的飞速发展,越来越多的设备、传感器和系统被连接到互联网,形成了一个庞大的、相互连接的智能网络。在这个网络中,物联网接入网关扮演着至关重要的角色,它不仅是连接物联网设备和云平台的桥梁,还是实现设…

数据结构-二叉树-堆(二)

一、建堆的时间复杂度问题 1、除了向上调整建堆,我们还可以向下调整建堆。不能在根上直接开始向下调整。这里的条件就是左右子树必须都是大堆或者小堆。我们可以倒着往前走,可以从最后一个叶子开始调整。但是从叶子开始调整没有意义。所以我们可以从倒数…

【华为OD机试】5G网络建设【C卷|200分】

【华为OD机试】-真题 !!点这里!! 【华为OD机试】真题考点分类 !!点这里 !! 题目描述 现需要在某城市进行5G网络建设,已经选取N个地点设置5G基站,编号固定为1到N, 接下来需要各个基站之间使用光纤进行连接以确保基站能互联互通,不同基站之间假设光纤的成本各不相同,且有…

mysql的多表查询和子查询

多表查询:查询数据时,需要使用多张表来查询 多表查询分类: 1.内连接查询 2.外连接查询 3.子查询 笛卡尔积: create table class (id int primary key auto_increment,name varchar(10) ); create table student (id int primar…

serdes 同轴电缆和双绞线接法

1、同轴电缆 Coaxial Cable 2、双绞线STP(Shielded Twisted Pair) 比如我们用的车载camera一般就只需要接一路即可,RIN接camera, RIN-通过电容接地。

python基础——正则表达式

📝前言: 这篇文章主要想讲解一下python中的正则表达式: 1,什么是正则表达式 2,re模块三匹配 3,元字符匹配 4,具体示例 🎬个人简介:努力学习ing 📋个人专栏&am…

qt中的取整函数

在Qt中,有以下几种常用的取整函数,用于处理浮点数的取整操作: 1. **qCeil()** - 向上取整: 该函数返回大于或等于给定浮点数的最小整数。如果输入值是正数,它会将小数部分去掉并增加到下一个整数;如果是…

重新理解React-hook

Hook是什么 Hook是React16.8版本新增的特性,它可以让我们在不写类组件的情况下使用state以及其他的React特性。 它解决了以下这些问题: 逻辑复杂的组件难以开发和维护,当我们的组件需要处理多个互不相关的local state时,每个生命周期函数中可能包含着各种互相关的逻辑类组…

认识HTTP

HTTP缺点 通信使用明文(不加密),内容可能会被窃听 不验证通信方的身份,可能遭遇伪装 无法证明报文的完整性,所以有可能遭篡改 一、通信使用明文(不加密),内容可能会被窃听 TCP/…

Git--基础学习--面向企业--持续更新

一、基础学习 1.1基本命令 //查询基础信息 git config --global --list //选取合适位置创建 mkdir 文件名 //创建文件夹 //全局配置 git config --global user.email "****e***i" git config --global user.name "*** K****"//--------------------进入…

西瓜书学习——线性判别分析

文章目录 定义LDA的具体步骤1. 计算类内散布矩阵(Within-Class Scatter Matrix)2. 计算类间散布矩阵(Between-Class Scatter Matrix)3. 求解最佳投影向量4. 数据投影5. 分类 定义 线性判别分析(Linear Discriminant A…

函数指针 和 指针函数

1、函数指针 函数指针是指向函数的指针变量。它可以用于存储函数的地址,允许在运行时动态选择要调用的函数。(是一个指针) //函数指针的形式 返回类型 (*指针变量名) 参数列表int add(int a,int b){return ab; }int subtract(int a,int b){…

mysql相关基础操作

安装 [rootlocalhost ~]# yum install mariadb-server mariadb -y # 自动监听3306端口 [rootlocalhost ~]# systemctl start mariadb.service [rootlocalhost ~]# systemctl enable mariadb.service [rootlocalhost ~]# netstat -lntup安全初始化 修改root用户的密码,新版本可…

php动态高亮web源代码

php动态高亮web源代码 注:配置好不允许高亮的文件名,安全第一 #php实现动态展示目录树结构源代码 适用于开放源代码,结合html缓存使用效果更佳,因循环较多不适合放首页 能力有限没实现行号 演示:show source|开放…

【Pytorch】NameError: name ‘Variable‘ is not defined

报错 NameError Traceback (most recent call last) Cell In[20], line 42 for epoch in range(num_epochs):3 if torch.cuda.is_available(): ----> 4 inputs Variable(x_train).cuda()5 target Variable(y_train).…

AWS云服务器选择哪个区域最好?

AWS每个区域都有特点,并无谁好谁坏的说法。云服务器最佳区域的选择取决于多个因素,包括业务需求、用户位置、数据合规性、延迟需求以及成本等。接下来是AWS的合作伙伴九河云提供的一些考虑因素和建议: (1)业务需求&am…

多家企业机密数据遭Lockbit3.0窃取,亚信安全发布《勒索家族和勒索事件监控报告》

本周态势快速感知 本周全球共监测到勒索事件87起,与上周相比勒索事件大幅下降。美国依旧为受勒索攻击最严重的国家,占比45%。 本周Cactus是影响最严重的勒索家族,Lockbit3.0和Bianlian恶意家族紧随其后,从整体上看Lockbit3.0依旧…

git 命令怎么回退到指定的某个提交 commit hash 并推送远程分支?

问题 如下图,我要回退到 【002】Babel 的编译流程 这一次提交 解决 1、先执行下面命令,输出日志,主要就是拿到提交 commit 的 hash,上图红框即可 git log或者 vscode 里面直接右击,copy sha 2、执行下面命令回退 g…

05_Scala运算符

文章目录 **1.Scala运算符****2.scala中没有 --等语法****3.逻辑运算符和Java完全相同****4.scala认为万物皆对象** 1.Scala运算符 Scala底层 使用的是equals() 程序员比较两个量的时候,谁来没事比较内存地址? Java中引用数据类型比较地址&#xff0…