synchronized关键字的使用和原理

synchronized关键字的使用和原理

synchronized:对象锁,保证了临界区内代码的原子性,采用互斥的方式让同一时刻至多只有一个线程能持有对象锁,其它线程获取这个对象锁时会阻塞,保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换。

1、使用方式:

锁对象:理论上可以是任意的唯一对象

package com.jtc.fe;public class synchronizedDemo {// 同步静态方法 ——> 锁住class类对象 ——> 全局唯一synchronized public static void func1(){}// 同步方法 ——> 锁住实例对象 ——> 每个实例都可以当锁synchronized public void func3(){}// 同步代码块public void func2(){// ——> 锁住class类对象 ——> 全局唯一synchronized (synchronizedDemo.class){}}public void func4(){// ——> 锁住实例对象 ——> 每个实例都可以当锁synchronized (this){}}public void func5(){// ——> 锁住字符串"123"synchronized ("123"){}}
}

注意:

synchronized 修饰的方法的不具备继承性,所以子类是线程不安全的,如果子类的方法也被 synchronized 修饰,两个锁对象其实是一把锁,而且是子类对象作为锁

2、锁原理

Java的对象由三部分组成:对象头 + 实例数据 + 对齐填充。

2.1、对象头

64位对象头由Mark Word、Class Pointer两部分组成,如果对象是数组,则还要加上数组长度,即三部分组成。在这里插入图片描述

Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。由于对象需要存储的运行时数据很多,但64位虚拟机给它的空间只有64位Bitmap,所以Mark Word被设计成一个有着动态定义的数据结构,即不同的锁状态存储内容不同。
在这里插入图片描述

如果要查看内存结构图,需要引入依赖:

<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version>
</dependency>
import com.example.demo.User;
import org.openjdk.jol.info.ClassLayout;public class p6 {static User user = new User();static User[] users = new User[10];public static void main(String[] args) {System.out.println(ClassLayout.parseInstance(user).toPrintable());System.out.println("*******************************************");System.out.println(ClassLayout.parseInstance(users).toPrintable());}
}

在这里插入图片描述

Mark Word由64位8个字节组成。Class Pointer由64位8个字节组成,但我们使用的64位 JVM会默认使用选项 +UseCompressedOops 开启指针压缩,将指针压缩至32位。即上面截图中的Class Pointer为4个字节32位。

如果在打印对象头前调用HashCode方法,则会变成如下:
在这里插入图片描述

从MarkWord的结构可以看出,在无锁阶段内存分布与上图是一一对应的,HashCode也是一一对应的。

2.2、锁升级

synchronized 是可重入、不公平的重量级锁,所以可以对其进行优化。

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁	// 随着竞争的增加,只能锁升级,不能降级
其中重量级锁还需要Monitor对象配合使用

偏向锁

偏向锁的思想是偏向于让第一个获取锁对象的线程,这个线程之后重新获取该锁不再需要同步操作:当锁对象第一次被线程获得的时候进入偏向状态,使用 CAS 操作将线程 ID 记录到 Mark Word。

轻量级锁

当有另外一个线程去尝试获取这个锁对象时,偏向状态就宣告结束,此时撤销偏向(Revoke Bias)后恢复到未锁定或轻量级锁状态。一个对象有多个线程要加锁,但加锁的时间是错开的(没有竞争),可以使用轻量级锁来优化,轻量级锁对使用者是透明的(不可见)。

重量级锁

在尝试加轻量级锁的过程中,CAS 操作无法成功,可能是其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁

在重量级锁阶段,每个 Java 对象都可以关联一个 Monitor 对象,Monitor 也是 class,其实例存储在堆中,如果使用 synchronized 给对象上锁之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针,这就是重量级锁。
在这里插入图片描述

工作流程:

  • 开始时 Monitor 中 Owner 为 null

  • 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor 中只能有一个 Owner,obj 对象的 Mark Word 指向 Monitor,把对象原有的 MarkWord 存入线程栈中的锁记录

  • 在 Thread-2 上锁的过程,Thread-3、Thread-4、Thread-5 也执行 synchronized(obj),就会进入 EntryList BLOCKED(双向链表)

  • Thread-2 执行完同步代码块的内容,根据 obj 对象头中 Monitor 地址寻找,设置 Owner 为空,把线程栈的锁记录中的对象头的值设置回 MarkWord

  • 唤醒 EntryList 中等待的线程来竞争锁,竞争是非公平的,如果这时有新的线程想要获取锁,可能直接就抢占到了,阻塞队列的线程就会继续阻塞

  • WaitSet 中的 Thread-0,是以前获得过锁,但条件不满足进入 WAITING 状态的线程(wait-notify 机制)

2.3、代码字节码

修饰代码段时:

public static void main(String[] args) {Object lock = new Object();synchronized (lock) {System.out.println("ok");}
}
0: 	new				#2		// new Object
3: 	dup
4: 	invokespecial 	#1 		
7: 	astore_1 				
8: 	aload_1					
9: 	dup						
10: astore_2 				
11: monitorenter 			// 【将 lock对象 MarkWord 置为 Monitor 指针】
12: getstatic 		#3		
15: ldc 			#4		
17: invokevirtual 	#5 		
20: aload_2 				
21: monitorexit 			// 【将 lock对象 MarkWord 重置, 唤醒 EntryList】
22: goto 30
25: astore_3 				
26: aload_2 				
27: monitorexit 			// 【将 lock对象 MarkWord 重置, 唤醒 EntryList】
28: aload_3
29: athrow
30: return
Exception table:from to target type12 22 25 		any25 28 25 		any
LineNumberTable: ...
LocalVariableTable:Start Length Slot Name Signature0 	31 		0 args [Ljava/lang/String;8 	23 		1 lock Ljava/lang/Object;

从上面我们可以看出:synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。其中两个 monitorexit 指令是为了保证锁在同步代码块代码正常执行以及出现异常的这两种情况下都能被正确释放。

修饰方法时:

public synchronized void method() {System.out.println("synchronized 方法");
}

JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,如果是实例方法,JVM 会尝试获取实例对象的锁。如果是静态方法,JVM 会尝试获取当前 class 的锁。

总结

synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,修饰方法是 ACC_SYNCHRONIZED 标识。

不过两者的本质都是对对象监视器 Monitor 的获取。
参考:https://github.com/Seazean/JavaNote/blob/main/Prog.md
https://blog.csdn.net/zhaocuit/article/details/100208879?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170239663116800182715111%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=170239663116800182715111&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-100208879-null-null.142v96pc_search_result_base9&utm_term=java%E5%AF%B9%E8%B1%A1%E5%A4%B4&spm=1018.2226.3001.4187

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

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

相关文章

【SpringBoot篇】Interceptor拦截器 | 拦截器和过滤器的区别

文章目录 &#x1f339;概念⭐作用 &#x1f384;快速入门⭐入门案例代码实现 &#x1f6f8;拦截路径&#x1f354;拦截器interceptor和过滤器filter的区别&#x1f386;登录校验 &#x1f339;概念 拦截器&#xff08;Interceptor&#xff09;是一种软件设计模式&#xff0c;…

【NSX-T】10. 搭建NSX-T环境 —— 使用 BGP 配置 Tier-0 网关

目录 10.1 创建上行链路网段10.2 创建 Tier-0 网关&#xff08;1&#xff09;设置 Interface 信息&#xff08;2&#xff09;设置 BGP添加 BGP 邻居 &#xff08;3&#xff09;设置 BGP 路由重分发设置路由重新分发 10.3 连接 Tier-0 和 Tier-1 网关10.4 使用网络拓扑验证 Tier…

《人工智能导论》知识思维导图梳理【第7章节】

文章目录 说明一 专家系统二 机器学习2.1 机器学习定义2.2 机器学习过程2.2.1 工作流程2.2.2 模型评估 2.3 机器学习分类在这里插入图片描述 机器学习部分md内容机器学习1 机器学习定义机器学习是从数据中自动分析获得模型&#xff0c;并利用模型对未知数据进行预测机器学习&am…

jpa 修改信息拦截

实现目标springbootJPA 哪个人&#xff0c;修改了哪个表的哪个字段&#xff0c;从什么值修改成什么值 import jakarta.persistence.*; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; im…

嵌入式开发、C++后端开发、C++音视频开发怎么选择?

嵌入式开发、C后端开发和C音视频开发的选择问题 近年来&#xff0c;随着互联网和物联网的快速发展&#xff0c;嵌入式开发、C后端开发和C音视频开发等职业领域也逐渐受到广泛关注。 对于有志于从事这些领域的人来说&#xff0c;如何做出选择呢&#xff1f;下面将从前景、薪酬和…

数据结构 之map/set练习

文章目录 1. 只出现一次的数字算法原理&#xff1a;代码&#xff1a; 2. 随机链表的复制算法原理&#xff1a;代码&#xff1a; 3. 宝石与石头算法原理&#xff1a;代码&#xff1a; 4. 坏键盘打字算法原理&#xff1a;代码&#xff1a; 5. 前K个高频单词算法原理&#xff1a;代…

kakfa实战指引-实时海量流式数据处理

前言 我们最终决定从头开始构建一些东西。我们的想法是&#xff0c;与其专注于保存成堆的数据&#xff0c;如关系数据库、键值存储、搜索索引或缓存&#xff0c;不如专注于将数据视为不断发展和不断增长的流&#xff0c;并围绕这个想法构建一个数据系统——实际上是一个数据架…

日本科学家们使用AI技术首次创造了世界上的心灵影像

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

圆通速递查询,圆通速递单号查询,一键复制查询好的物流信息

批量查询圆通速递单号的物流信息&#xff0c;并将查询好的物流信息一键复制出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 圆通速递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的朋友记得先注册&…

网站监控/定时任务/网站网址URL状态监控神器

源码介绍&#xff1a; 这是一款在线监控网址的源码&#xff0c;对于有多个网站的站长来说还是非常有用的。也可以做为一项收费服务对外提供。这个程序没啥问题&#xff0c;就是UI有点简单&#xff0c;不影响使用。 网站监控/定时任务/网站网址URL状态监控神器&#xff0c;可以…

【python】Debian安装miniconda、spyder、tushare

1. miniconda 安装 — 动手学深度学习 2.0.0 documentation中有安装Miniconda的一些说明。 Miniconda — miniconda documentation是Miniconda网站&#xff0c;里面也有安装说明。 Debian安装按照linux安装即可&#xff1a; mkdir -p ~/miniconda3 wget https://repo.anaco…

打开软木塞,我们来谈谈葡萄酒泡泡吧

香槟是任何庆祝场合的最佳搭配。从婚礼和生日到单身派对和典型的周五晚上&#xff0c;这款气泡饮料是生活中特别聚会的受欢迎伴侣。 来自云仓酒庄品牌雷盛红酒分享你知道吗&#xff0c;你喜欢喝的那瓶香槟酒可能根本不是香槟&#xff0c;而是汽酒&#xff1f;你不是唯一一个认…