深入浅出synchronized关键字

news/2024/5/2 10:59:41/文章来源:https://blog.csdn.net/qq_36933421/article/details/128410836

前言

无论在日常工作还是面试过程中,synchronized关键字作为并发场景下的操作,是一定要掌握的,本文从synchronized的使用方式、原理及优化三个方面,对synchronized关键字作一个系统化的说明。

使用方式

synchronized主要有三种使用方式,分别是

  • 修饰普通方法,锁作用于当前对象
  • 修饰静态方法,锁作用于类
  • 修饰代码块,锁作用于当前对象实例,需要指定加索的对象

1、修饰普通方法

当synchronized关键字加到普通方法上时,这个方法就被加上了同步锁,这也使得某一时间只有一个线程可以访问该方法。

package com.example.dailyrecords.demo;import java.util.Date;/*** @author zy* @version 1.0.0* @ClassName synchronizedTest.java* @Description TODO* @createTime 2022/12/22*/
public class synchronizedTest {public synchronized void test(){try{System.out.println(Thread.currentThread().getName()+new Date());Thread.sleep(5000);System.out.println("结束"+new Date());}catch (Exception e){e.printStackTrace();}}public static void main(String[] args) throws Exception {//初始化synchronizedTest synchronizedTest = new synchronizedTest();for (int i = 1; i < 3; i++) {new Thread(()->{synchronizedTest.test();},String.valueOf(i)+"号线程").start();}}
}

在这里插入图片描述

上面定义了一个普通方法test,然后开启两个线程去执行test方法,由于加上了synchronized关键字,因此,某一时间只有一个线程获取到锁并执行,另一个线程被阻塞,直到获取锁的线程释放锁,另一个才执行。
在这里插入图片描述

2、修饰静态方法

由于静态方法是在类初始化的时候加载的,因此synchronized关键字也就在类初始化时作用到了当前类对象上,因此锁住的是整个类。

package com.example.dailyrecords.demo;import java.util.Date;/*** @author zy* @version 1.0.0* @ClassName synchronizedTest.java* @Description TODO* @createTime 2022/12/22*/
public class synchronizedTest {public static synchronized void test(){try{System.out.println(Thread.currentThread().getName()+new Date());Thread.sleep(50000);System.out.println("结束"+new Date());}catch (Exception e){e.printStackTrace();}}public static void main(String[] args){for (int i = 1; i < 3; i++) {new Thread(()->{test();},String.valueOf(i)+"号线程").start();}}
}

在这里插入图片描述

3、修饰代码块

synchronized的锁的粒度能不能更小呢?那就是锁住一块代码块,如下所示

public void test1(){synchronized (this){//代码块}}

这里锁住的就是括号里面的内容,这里的synchronized (this),代表着只有当前对象才可以访问这段代码。

原理

synchronized作为一个关键字,它的底层是通过monitor监视器锁实现的。我们先将代码编译得到字节码文件,javac xxx.java,然后通过javap -v xxx.class查看字节码命令,如下

public class com.example.dailyrecords.demo.synchronizedTest2minor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #6.#21         // java/lang/Object."<init>":()V#2 = Class              #22            // com/example/dailyrecords/demo/synchronizedTest2#3 = Methodref          #2.#21         // com/example/dailyrecords/demo/synchronizedTest2."<init>":()V#4 = Methodref          #2.#23         // com/example/dailyrecords/demo/synchronizedTest2.method1:()V#5 = Methodref          #2.#24         // com/example/dailyrecords/demo/synchronizedTest2.method2:()V#6 = Class              #25            // java/lang/Object#7 = Utf8               <init>#8 = Utf8               ()V#9 = Utf8               Code#10 = Utf8               LineNumberTable#11 = Utf8               method1#12 = Utf8               method2#13 = Utf8               StackMapTable#14 = Class              #22            // com/example/dailyrecords/demo/synchronizedTest2#15 = Class              #25            // java/lang/Object#16 = Class              #26            // java/lang/Throwable#17 = Utf8               main#18 = Utf8               ([Ljava/lang/String;)V#19 = Utf8               SourceFile#20 = Utf8               synchronizedTest2.java#21 = NameAndType        #7:#8          // "<init>":()V#22 = Utf8               com/example/dailyrecords/demo/synchronizedTest2#23 = NameAndType        #11:#8         // method1:()V#24 = NameAndType        #12:#8         // method2:()V#25 = Utf8               java/lang/Object#26 = Utf8               java/lang/Throwable
{public com.example.dailyrecords.demo.synchronizedTest2();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 10: 0public synchronized void method1();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=0, locals=1, args_size=10: returnLineNumberTable:line 13: 0public void method2();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=3, args_size=10: aload_01: dup2: astore_13: monitorenter4: aload_15: monitorexit6: goto          149: astore_210: aload_111: monitorexit12: aload_213: athrow14: returnException table:from    to  target type4     6     9   any9    12     9   anyLineNumberTable:line 15: 0line 17: 4line 18: 14StackMapTable: number_of_entries = 2frame_type = 255 /* full_frame */offset_delta = 9locals = [ class com/example/dailyrecords/demo/synchronizedTest2, class java/lang/Object ]stack = [ class java/lang/Throwable ]frame_type = 250 /* chop */offset_delta = 4public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: new           #2                  // class com/example/dailyrecords/demo/synchronizedTest23: dup4: invokespecial #3                  // Method "<init>":()V7: astore_18: aload_19: invokevirtual #4                  // Method method1:()V12: aload_113: invokevirtual #5                  // Method method2:()V16: returnLineNumberTable:line 21: 0line 22: 8line 23: 12line 24: 16
}

可以看到,method2()方法加上synchronized关键字之后,添加了monitorenter和monitorexit命令,monitorenter存在于同步代码块开始的位置,而monitorexit存在于同步代码块结束的位置,它们分别代表着获取锁和释放锁。每一个monitorenter都必须对应一个monitorexit。
在这里插入图片描述
monitor主要由计数器count、阻塞线程集合_EntryList、释放锁线程集合_WaitSet和持有锁_Owner构成,当没有线程获取这把锁的时候,count值为0,如果有一个线程获取这把锁,它的值就会+1,并且设置该线程为锁的持有者。_owner指向的就是当前持锁线程。如果该线程已经占用该锁,并且重新进入,那么count的值就会+1。当执行到monitorexit的时候,count的值就会-1,直到count值为0的时候,该持锁线程会进入到WaitSet里面,将状态改为等待状态,让其他处于EntryList里的阻塞线程重新自旋获取这把锁。

锁的优化

这里主要对锁升级做一些说明,可能大家也都了解自旋锁、偏向锁、轻量级锁和重量级锁这个升级过程,下面详细说明。

自旋锁

当一个线程在获取锁的时候,如果该锁已被其它线程获取到,那么该线程就会去循环自旋获取锁,不停地判断该锁是否能够已经被释放,自选直到获取到锁才会退出循环。通常该自选在源码中都是通过for(; ;)或者while(true)这样的操作实现,但是如果一直自旋下去,也会造成CPU资源的浪费,因此,当自旋次数超过一定次数后,这个线程就会被挂起。

偏向锁(可重入锁)

首先,一个对象在内存中由对象头、示例数据和数据填充三部分组成。当一个线程获取锁之后,这个锁对象在对象头中就会记录这个线程的ID,之所以偏向就是因为有这个ID,如果后面还是这个线程进入和退出同步时,只要检查是否是这个偏向的线程ID即可。

轻量级锁

由偏向锁升级而来,当一个线程获取到锁后,此时这把锁是偏向锁,此时如果有第二个线程来竞争锁,偏向锁就会升级为轻量级锁,底层通过自旋来实现,并不会阻塞线程,需要强调的是,轻量级锁并不是用来代替重量级锁的。引入轻量级锁的目的在于:在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗,但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁。

重量级锁

如果自旋多次仍然没能获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞,除了持有锁的线程其他全部阻塞,防止CPU空转

锁升级

synchronized锁升级是同过修改对象头来实现的
偏向锁:线程1第一次进入同步代码块的线程,将对象头的线程id修改成自己的id,此时是偏向锁,偏向锁不会自动释放锁,以后线程1再次请求就无需加锁解锁
轻量锁:线程2要竞争锁对象,而因为偏向锁不会自动释放锁,因此对象头的线程id还是线程1的id,此时需要需要查看线程1是否存活,通过cas来判断若不存活,锁对象置为无锁状态,线程2竞争锁设置为偏向锁;若存活,查看线程1的栈帧信息,若需要继续持有这个锁对象,那么暂停线程1,撤销偏向锁,升级为轻量级锁;若不需要持有,那么将锁对象设为无锁状态,重新偏向线程
重量锁:如果自旋次数到达且线程1还没有释放锁,又或者一直自旋,此时又有其他线程来竞争锁,轻量锁就会膨胀为重量锁,重量锁会将未获取到锁的线程阻塞,防止CPU空转

锁消除

在JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁,例如下面代码片段

public void method() {synchronized (new Object()) {//代码逻辑}}

这段代码里面,我们new了一个Object对象来作为锁对象,但是这个对象也只有在method( )中被使用,其完整的生命周期都在这个方法中,也就是说JVM在经过逃逸分析后会对它进行栈上分配,由于在底层变成了私有数据,那么也就无需加锁了。

锁粗化

在JIT编译时,发现如果有一段代码中频繁的加锁释放锁,会将前后的锁合并为一个锁,避免频繁加锁释放锁,例如下面的代码片段

public void method() {for(int i = 0;i < 100; i++) {synchronized (new Object()) {//代码逻辑}}}

如果按照正常的synchronized步骤走,这个循环需要进行多次的加锁解锁操作,当这段代码在即时编译时,JVM检测到每一次都是对同一个对象加锁,那么就会把这一串连续频繁的加锁解锁操作优化成仅仅一次公共的加锁解锁操作。

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

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

相关文章

Java: static,final,代码块 的详解

Java: static&#xff0c;final&#xff0c;代码块 的详解 每博一文案 山本文绪说过这样一句话&#xff1a;哪些决定放弃了的事&#xff0c;就请放弃得干干净净。哪些决定再也不见面的人&#xff0c;就真 的不要再见面了&#xff0c;不要再做背叛自己的事&#xff0c;如果想要…

操作手册(GB8567——88)基于协同的在线表格forture-sheet

操作手册&#xff08;GB8567——88&#xff09; 1引言 1.1编写目的 为了帮助用户更好的上手本系统&#xff0c;加快用户对forture-sheet在线表格的快速入门&#xff0c;本操作手册详细介绍使用forture-sheet的部分基础操作以及注意细节。 1.2前景 待开发系统的名称&#x…

ASP.NET开发的医疗健康咨询平台源码 养生知识咨询 寻根问药平台源码 C#源码

一、源码特点&#xff1a; 爱心医生健康知识门户网站是一个权威的医疗科普视频、语音、知识、医疗健康问答平台。 包含所有源代码和数据库&#xff0c;可以直接部署到IIS中使用。 二、菜单功能 网站页面&#xff1a; 1、首页&#xff1a;包含幻灯片。 2…

InnoDB详解2

文章目录InnoDB详解21 行格式1 Compact行格式详解1 变长字段长度列表&#xff08;两个字节&#xff09;2 NULL值列表&#xff08;1个字节&#xff09;3 记录头信息 &#xff08;重点&#xff09;2 Dynamic行格式2 页的上层结构InnoDB详解2 1 行格式 规定每条记录是怎么存储的…

解决资源消耗,top的运用记录

第一条命令uptime load average 后面的三个数字&#xff0c;分别代表1分钟、5分钟和15分钟内机器的平均负载 使用top命令解决负载问题 Cpu(s)这一行提供了CPU运行情况信息 这些缩写分别代表了不同含义 (1)us&#xff1a;用户CPU时间 运行非优雅的用户进程所占CPU时间的百…

Python学习笔记(十九)——Matplotlib入门上

目录 Matplotlib简介 导入matplotlib模块 图的参数说明 matplotlib图像组成部分介绍 matplotlib绘图步骤分析 matplotlib实现简单图像 matplotlib画布 画布-plt.figure() 实例 同一画布制作多张图像 创建多个子图 实例 plt.subplots 相关参数 调整subplot周围的间距…

简单记录一下怎么看package.json文件

首先每个vue工程文件从仓库克隆代码下来的时候&#xff0c;一般都会包含这个文件&#xff0c;这个文件非常重要&#xff0c;package.json包含了关于项目重要信息&#xff0c;如下图所示 其中包含了name、version、description、author、scripts、dependencies、devDependencies…

小结 | 决策树

一.基本原理 决策树是一种树状结构模型&#xff0c;每一个根节点都是一个特征判断&#xff0c;它的叶子节点就是它的特征分类结果 决策树是一种分类和回归的基本模型&#xff0c;是一棵树的形式&#xff0c;其实就是将平时所说的 if-else 语句构建成了树的形式。决策树主要包…

短视频引流+私域流量沉淀,一个全新的短视频和链动模式结合方案

在微盟企微助手微盟智慧零售团队的协助下&#xff0c;今年7月底么么茶正式开始运营企微私域&#xff0c;截至当前&#xff0c;在短短3个月时间已成功沉淀7万私域客户&#xff0c;线上商城GMV超145万。 么么茶旅拍的核心流量来源自公域短视频平台&#xff0c;品牌基于服务覆盖下…

deck.gl 调研

0 结论 deck gl 是基于 WebGL 的数据可视化框架&#xff0c;可以集成在主流的地图框架&#xff08;arcgis&#xff0c;google maps&#xff0c;mapbox &#xff09;中使用&#xff0c; 也可以单独使用。 deck gl 通过layer进行数据可视化&#xff0c;支持多种展示效果&#xf…

ASP.NET开源版MES加工装配模拟系统源码/WinForm工厂加工装配系统源码/流程工序管理

一、源码描述 本系统用户大学机械科上位机加工装配模拟实验&#xff0c;目前正常用于实验当中。环境&#xff1a;VS2010(C# .NET4.0,多层结构)、sqlserver2008 r2 &#xff1b;Winform;使用到RFID读写器&#xff08;设备是可以变更的&#xff0c;修改RFID.Library项目的…

数字三角形问题

数字三角形问题一、题目描述二、题目分析1、问题分析2、思路分析&#xff08;1&#xff09;状态转移方程状态表示状态转移&#xff08;2&#xff09;循环的设计三、代码实现一、题目描述 二、题目分析 1、问题分析 这道题给我们的第一眼感觉就是情况太多了&#xff0c;太复杂…

【TypeScript】常用类型声明详情概述

目录 TypeScript常用类型 类型注解 TS类型概述 原始类型 数组类型 对象类型 函数类型 类型别名 接口 元组 字面量类型 枚举 any类型 typeof操作符 类型推论 类型断言 TypeScript常用类型 TypeScript是JS的超集&#xff0c;TS提供了JS的所有功能&#xff0c;并额…

PyInstaller的常用打包命令

学习了pyqt后&#xff0c;设计了界面&#xff0c;并且需要打包为exe程序。 每次打包时&#xff0c;都要查好久资料&#xff0c;故此记录一下常用的命令。 PyInstaller 是一个 Python 应用程序打包工具&#xff0c;它可以将 Python 程序打包为单个独立可执行文件。 要使用 P…

11Python面相对象基础语法

面相对象基础语法 01. dir 内置函数 在 Python 中 对象几乎是无所不在的&#xff0c;我们之前学习的 变量、数据、函数 都是对象 在 Python 中可以使用以下两个方法验证&#xff1a; 使用内置函数 dir 传入 标识符 / 数据&#xff0c;可以查看对象内的 所有属性及方法 提示…

虚拟机docker网络问题处理

问题 我们有2台设备&#xff0c;ip 为 172.20.30.1 172.20.30.2 &#xff0c;虚拟机上的服务需要连接这2台设备&#xff0c;网络已经做通了&#xff0c;可以正常连接虚拟机异常关闭&#xff0c;重新开启后。发现服务有些问题&#xff0c;就打算将docker服务重新部署&#xff0…

面渣逆袭:Java并发六十问,快来看看你会多少道

这篇文章有点长&#xff0c;四万字&#xff0c;图文详解六十道Java并发面试题。人已经肝麻了&#xff0c;大家可以点赞、收藏慢慢看&#xff01;扶我起来&#xff0c;我还能肝&#xff01; 基础 1.并行跟并发有什么区别&#xff1f; 从操作系统的角度来看&#xff0c;线程是…

善康医药冲刺科创板上市:计划募资13亿元,上半年亏损5000万元

近日&#xff0c;深圳善康医药科技股份有限公司&#xff08;下称“善康医药”&#xff09;在上海证券交易所递交招股书&#xff0c;准备在科创板上市。本次冲刺上市&#xff0c;善康医药计划募资13.27亿元&#xff0c;将用于新药研发项目、创新药高端制剂生产基地建设项目、营销…

Influxdb双写服务influxdb-relay部署配置【离线】

Background Influxdb社区版未提供集群方案&#xff0c;官方提供的集群模式为闭源收费版本&#xff0c;具体收费明细不太清楚哈&#xff0c;有知道的请留言告知哈。官方开源的influxdb-relay仅仅支持双写功能&#xff0c;并未支持负载均衡能力&#xff0c;仅仅解决了数据备份的问…

Simulink代码生成: Switch模块及其代码

本文描述Switch模块的建模并研究生成的代码。 文章目录1 Simulink中的Switch模块2 Switch模块建模及代码生成3 Switch模块其他用法3.1 多重Switch3.2 通过标定量Switch4 总结1 Simulink中的Switch模块 在Simulink中Switch模块时非常常见的&#xff0c;通常用于根据一定地条件选…