JUC并发编程——对于synchronized关键字的理解

news2024/11/29 8:44:27

现象🔍:

两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,最后输出的 counter一定为0 吗?

@Slf4j(topic = "c.Test17")
public class Test17 {
    static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter++;
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter--;
            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        log.debug("{}", counter);
    }
}

分析🤔

以上的结果可能是正数、负数、零。为什么呢?

因为 Java 中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码来进行分析。
例如对于 i++ 指令(i 为静态变量),实际会产生的字节码指令:

getstatic  	i 	// 获取静态变量i的值
iconst_1 		// 准备常量1
iadd 			// 自增
putstatic  	i   // 将修改后的值存入静态变量i

而对应 i-- 也是类似:

getstatic  	i 	// 获取静态变量i的值
iconst_1 		// 准备常量1
isub 			// 自减
putstatic  	i   // 将修改后的值存入静态变量i

在多线程情况下,这八行代码可能会交错执行。

解决方法

使用 synchronized 加锁,保证了代码块内的原子性

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            counter++;
        }
    }, "t1");

    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            counter--;
        }
    }, "t2");

    t1.start();
    t2.start();
    t1.join();
    t2.join();

    log.debug("{}", counter);
}

Java虚拟机的指令集中 有monitorentermonitorexit两条指令来支持synchronized关键字的语义。

根据《Java虚拟机规范》的要求,
在执行monitorenter指令时,首先要去尝试获取对象的锁。如果这个对象没被锁定,或者当前线程已经持有了那个对象的锁,就把锁的计数器的值增加一,而在执行monitorexit指令时会将锁计数器的值减一。一旦计数器的值为零,锁随即就被释放了。如果获取对象锁失败,那当前线程就应当被阻塞等待,直到请求锁定的对象被持有它的线程释放为止。

  • 被synchronized修饰的同步块对同一条线程来说是可重入的。这意味着同一线程反复进入同步块也不会出现自己把自己锁死的情况。

    例如在切换上下文进程时,可能带有锁的A线程并没有释放,但切换到了没有锁的B线程,不能进入synchronized块内,当轮到A线程时,A线程会带有那把锁的钥匙,再次进入synchronized块内,直到释放锁。

  • 被synchronized修饰的同步块在持有锁的线程执行完毕并释放锁之前,会无条件地阻塞后面其他线程的进入。
    这意味着无法像处理某些数据库中的锁那样,强制已获取锁的线程释放锁;也无法强制正在等待锁的线程中断等待或超时退出。

如果要阻塞或唤醒一条线程,则需要操作系统来帮忙完成,这就不可避免地陷入用户态到核心态的转换中,进行这种状态转换需要耗费很多的处理器时间。状态转换的时间甚至比执行用户代码的时间还长。

方法上的 synchronized

class Test{
	public synchronized void test(){
	}
}	
// 等价于
class Test{
	public void test(){
		synchronized (this){		
		}
	}
}	
class Test{
	public synchronized static void test(){
	}
}	
// 等价于
class Test{
	public void test(){
		synchronized (Test.class){		
		}
	}
}	

Synchronized 的底层原理

1)对象头

想要理解 synchronized 的工作流程,需要对HotSpot虚拟机对象的内存布局(尤其是对象头部分)有所了解。HotSpot 虚拟机的对象头(Object Header)分为两部分,第一部分用于存储对象自身的运行时数据(Mark Word)和用于存储指向方法区对象类型数据的指针(类指针)。

由于对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到Java虚拟机的空间使用效率,Mark Word被设计成一个非固定的动态数据结构,以便在极小的空间内存储尽量多的信息。它会根据对象的状态
复用自己的存储空间。在32位, 64位操作系统中的空间不一样,这里以32位的操作系统为例。

在这里插入图片描述

2)Monitor 原理

Monitor 被翻译为监视器管程,Monitor 是操作系统管理的。synchronized 底层会被 Monitor 管理。
在这里插入图片描述

  • 刚开始 Monitor 中 Owner 为 null
  • 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一个 Owner
  • 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入 EntryList BLOCKED
  • Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的

3)synchronized 的底层原理

static final Object lock = new Object();
static int counter = 0;

public static void main(String[] args) {
	synchronized (lock) {
		counter++;
	}
}

对应的字节码为

public static void main(java.lang.String[]);
 descriptor: ([Ljava/lang/String;)V
 flags: ACC_PUBLIC, ACC_STATIC
	Code:
		 stack=2, locals=3, args_size=1
			 0: getstatic 		#2 	// <- lock引用 (synchronized开始)
			 3: dup
			 4: astore_1 			// lock引用 -> slot 1
			 5: monitorenter 		// 将 lock对象 MarkWord 置为 Monitor 指针
			 6: getstatic 		#3 	// <- i
			 9: iconst_1 			// 准备常数 1
			 10: iadd 				// +1
			 11: putstatic 		#3 	// -> i
			 14: aload_1 			// <- lock引用
			 15: monitorexit 		// 将 lock对象 MarkWord 重置, 唤醒 EntryList
			 16: goto 24
			 19: astore_2 			// e -> slot 2 
			 20: aload_1 			// <- lock引用
			 21: monitorexit 		// 将 lock对象 MarkWord 重置, 唤醒 EntryList
			 22: aload_2 			// <- slot 2 (e)
			 23: athrow 			// throw e
			 24: return
	 Exception table:
		 from to target type
		   6  16   19 	any
		  19  22   19   any
	 LineNumberTable:
		 line 8: 0
		 line 9: 6
		 line 10: 14
		 line 11: 24
	 LocalVariableTable:
		 Start Length Slot Name Signature
		 0 		 25 	0  args [Ljava/lang/String;
	 StackMapTable: number_of_entries = 2
		 frame_type = 255 /* full_frame */
			 offset_delta = 19
			 locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
			 stack = [ class java/lang/Throwable ]
		 frame_type = 250 /* chop */
		 	offset_delta = 4

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

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

相关文章

Js逆向简单分析-某网站登录案例

文章目录 概要整体流程1.打开网站&#xff0c;输入数据进行登录2.对getlogin.php进行查看分析3.对于登录请求连接进行断点调试4.堆栈跟踪5.在网络上找在线md5加密 友情推荐 概要 某网站登录流量包逆向分析。 整体流程 1.打开网站&#xff0c;输入数据进行登录 用户名&#…

【C语言基础】:自定义类型(一)--> 结构体

文章目录 一、内置类型与自定义类型1.1 内置类型&#xff08;基本数据类型&#xff09;1.2 自定义类型 二、结构体2.1 结构体的声明2.2 结构体变量的创建和初始化2.3 结构体的特殊声明2.4 结构体的自引用 三、结构体内存对齐3.1 对齐规则3.2 为什么存在内存对齐3.3 修改默认对齐…

基于java+SpringBoot+Vue的校友社交系统设计与实现

基于javaSpringBootVue的校友社交系统设计与实现 开发语言: Java 数据库: MySQL技术: SpringBoot MyBatis工具: IDEA/Eclipse、Navicat、Maven 系统展示 前台展示 后台展示 系统简介 整体功能包含&#xff1a; 校友社交系统是一个为校友提供一个交流互动、信息共享的平台…

【高数】汤家凤高等数学辅导讲义+1800错题整理

第一章 极限与连续 1. 2. 3. 4. 5. 6. 7. 第二章 导数与微分 高等数学辅导讲义 1. 2. 3. 4. 5. 6. 7. 8. 1800 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 第三章 一元函数微分学的应用 高等数学辅导讲义 中值定理 题型一 题型二 题型三 题型四 题型五 单调性、极值与…

三菱GX WORKS3连接FX5U系列PLC时,弹出窗口提示:用户认证功能或安全性强化模式未启用

三菱GX WORKS3连接FX5U系列PLC时&#xff0c;弹出窗口提示&#xff1a;用户认证功能或安全性强化模式未启用 如下图所示&#xff0c;使用GX WORKS3编程软件连接FX5U系列PLC&#xff0c; 首先&#xff0c;在连接目标中选择自己当前使用的网卡适配器&#xff0c;并将IP地址设置在…

HuTool工具箱验证JWT生成Token失败

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于…

IC-随便记

1、移远通信---通信模组 物联网解决方案供应商&#xff0c;可提供完备的IoT产品和服务&#xff0c;涵盖蜂窝模组(5G/4G/3G/2G/LPWA)、车载前装模组、智能模组&#xff08;5G/4G/边缘计算&#xff09;、短距离通信模组(Wi-Fi&BT)、GNSS定位模组、卫星通信模组、天线等硬件产…

【全栈小5】我的创作纪念日

目录 前言机缘收获粉丝和原创个人成就六边形战士 回顾文章原代码代码优化 憧憬 前言 全栈小5 &#xff0c;有幸再次遇见你&#xff1a; 还记得 2019 年 03 月 29 日吗&#xff1f; 你撰写了第 1 篇技术博客&#xff1a; 《前端 - 仿动态效果 - 展开信息图标》 在这平凡的一天&…

直播上瘾?

目前我们正处在日新月异高速发展的时代&#xff0c;各行各业都在接入 AI&#xff0c;各行各业都在涌向直播的时代。当然&#xff0c;历史的车轮不会因为个人的喜好而改变&#xff0c;我们唯一能做的就是拥抱变化&#xff0c;这样才不会活的很别扭。 “ PS&#xff1a;这就是我为…

网络安全:绕过 MSF 的一次渗透测试

这次渗透的主站是 一个 Discuz!3.4 的搭建 违法招 piao 网站&#xff0c; 配置有宝塔 WAF 用 Discuz!ML 3.X 的漏洞进行攻击&#xff0c;但是没有成功 发现主站外链会有一个发卡网&#xff0c;引导人们来这充值&#xff0c;是 某某发卡网&#xff0c;而且域名指向也是主站的 ip…

一篇文章带你搞定企业级完整性能测试流程!

大部分公司在最初试的阶段只会关心项目的基本功能&#xff0c;能用就可以。但是随着项目的成熟&#xff0c;用户量逐步的增大&#xff0c;线上经常就会出现一些系统崩溃&#xff0c;用户反映系统太慢等性能问题的爆发。所以&#xff0c;性能测试的需求就逐步变得迫切了。所以&a…

照片分享,欢迎家庭新成员HPE ProLiant DL580 Gen9

正文共&#xff1a;1234 字 29 图&#xff0c;预估阅读时间&#xff1a;1 分钟 距离上一台服务器HPE ProLiant DL360 Gen9开箱已经过去4年了&#xff0c;回忆满满&#xff08;风雨同舟&#xff0c;感谢HP Proliant DL360 Gen9陪我走过的四年&#xff09;&#xff1b;就在上周&a…

Kubernetes篇(二)— 集群环境搭建

目录 前言一、 环境规划集群类型安装方式主机规划 二、环境搭建主机安装环境初始化安装docker安装kubernetes组件准备集群镜像集群初始化安装网络插件 三、 服务部署 前言 本章节主要介绍如何搭建kubernetes的集群环境 一、 环境规划 集群类型 kubernetes集群大体上分为两类…

Java基础之运算符(整合)

文章目录 一.运算符算数运算符练习: 二.算术运算符的高级用法""操作的三种情况数字相加字符串相加字符相加 三.自增自减运算符基本用法 四.赋值运算符&关系运算符赋值运算符关系运算符逻辑运算符 五.短路逻辑运算符六.三元运算符 一.运算符 运算符: 对字面量或…

canvas画图,画矩形,圆形,直线,曲线可拖拽移动

提示&#xff1a;canvas画图&#xff0c;画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖拽移动 文章目录 前言一、画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖拽移动总结 前言 一、画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖…

计算机基础系列 —— 虚拟机代码翻译器(1)

“Most good programmers do programming not because they expect to get paid or get adulation by the public, but because it is fun to program.” ―Linus Torvalds 文中提到的所有实现都可以参考&#xff1a;nand2tetris_sol&#xff0c;但是最好还是自己学习课程实现一…

Adaboost集成学习 | Matlab实现基于LSTM-Adaboost长短期记忆神经网络结合Adaboost集成学习时间序列预测(股票价格预测)

目录 效果一览基本介绍模型设计程序设计参考资料效果一览 基本介绍 Adaboost集成学习 | Matlab实现基于LSTM-Adaboost长短期记忆神经网络结合Adaboost集成学习时间序列预测(股票价格预测) 模型设计 股票价格预测是一个具有挑战性的时间序列预测问题,可以使用深度学习模型如…

【Vue】动态样式

内联样式的动态样式 body(){ boxASelect:false, } v-bind:style"{borderColor:boxASelect ? red : #ccc}" <body><header><h1>Vue Dynamic Styling</h1></header><section id"styling"><div class"demo&quo…

HarmonyOS像素转换-如何使用像素单位设置组件的尺寸。

1 卡片介绍 基于像素单位&#xff0c;展示了像素单位的基本知识与像素转换API的使用。 2 标题 像素转换&#xff08;ArkTS&#xff09; 3 介绍 本篇Codelab介绍像素单位的基本知识与像素单位转换API的使用。通过像素转换案例&#xff0c;向开发者讲解了如何使用像素单位设…

JUC/多线程 模式(四)

一、同步模式之保护性暂停 即 Guarded Suspension &#xff0c;用在一个线程等待另一个线程的执行结果 产生结果的线程和使用结果的线程是一一对应的&#xff0c;有多少个生产结果的线程就有多少个使用结果的线程。 要点 有一个结果需要从一个线程传递到另一个线程&#xff0…