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

现象🔍:

两个线程对初始值为 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

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

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

相关文章

可望而不可即的“人文关怀”

死亡既然是最后的归宿&#xff0c;生命的必然&#xff0c;自然也就没有必要过多地害怕了。一切顺其自然&#xff0c;交给“命运”就是了。 我参观过英国的临终关怀医院&#xff0c;这是世界上最早的一所临终关怀医院&#xff0c;已有100多年历史。 那里的大多数病人都只剩一个…

第1章.提示词:开启AI智慧之门的钥匙

什么是提示词&#xff1f; 提示词&#xff0c;是引导语言模型的指令&#xff0c;让用户能够驾驭模型的输出&#xff0c;确保生成的文本符合需求。 ChatGPT&#xff0c;这位文字界的艺术大师&#xff0c;以transformer架构为基石&#xff0c;能轻松驾驭海量数据&#xff0c;编织…

【机器学习之---数学】马尔科夫链

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 马尔科夫 1. 概念 1.1 引言 马尔可夫链在许多领域都有应用&#xff0c;包括物理学、生物学、工程学、经济学和计算机科学等。在计算机科学中&#xff0…

QT使用数据库

数据库就是保存数据的文件。可以存储大量数据&#xff0c;包括插入数据、更新数据、截取数据等。用专业术语来说&#xff0c;数据库是“按照数据结构来组织、存储和管理数据的仓库”。 什么时候需要数据库&#xff1f;在嵌入式里&#xff0c;存储大量数据&#xff0c;或者记录数…

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

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

(C语言) fgetc与fputc函数详解

目录 1 fgetc函数详解 1.1 从文件流中读取数据 1.2 从标准输入流中读取数据 2 fputc函数详解 2.1 向文件流中写入数据 2.2 向标准输出流中写入数据 1 fgetc函数详解 头文件&#xff1a;stdio.h 该函数只有一个参数&#xff1a;stream 作用&#xff1a;从输入流中获得一个…

Gif动态图片如何制作?悄悄告诉你两招就能做!

怎么制作gif动态图片&#xff1f;Gif动图在我们的日常生活中非常的常见&#xff0c;尤其是在各大聊天软件中。当我们想要自己制作这种有趣的gif动图的时候要怎么办呢&#xff1f;很简单&#xff0c;制作gif动图的方法通常有两种&#xff0c;一种是视频转换gif另一种就是图片合成…

最大子序列(蓝桥杯,acwing,单调队列)

题目描述&#xff1a; 输入一个长度为 n 的整数序列&#xff0c;从中找出一段长度不超过 m 的连续子序列&#xff0c;使得子序列中所有数的和最大。 注意&#xff1a; 子序列的长度至少是 1。 输入格式&#xff1a; 第一行输入两个整数 n,m。 第二行输入 n 个数&#xff0…

CATSploit:一款基于CATS的自动化渗透测试执行工具

关于CATSploit CATSploit是一款基于CATS的自动化渗透测试执行工具&#xff0c;该工具基于网络攻击技术评分&#xff08;CATS&#xff09;方法实现其功能&#xff0c;可以在无需渗透测试人员操作的情况下&#xff0c;自动对目标应用执行安全渗透测试。 在执行渗透测试的过程中&…

【leetcode】力扣简单题两数之和

题目 思路 代码实现 #include<iostream> #include<unordered_map>using namespace std;class Solution { public:vector<int> TwoNumber(const vector<int>& nums, int target){vector<int> number_vector;unordered_map<int, int> …

【PyQt学习篇 · ⑮】:qrc/rcc资源系统

文章目录 qrc使用介绍rcc编译资源rcc 的安装与基本使用 编译成Python文件使用资源系统文件方式一&#xff1a;导入资源系统文件方式二&#xff1a;整合资源系统文件 qrc使用介绍 在PyQt中&#xff0c;qrc文件是一种资源文件&#xff0c;用于将应用程序所需的资源&#xff08;如…

pmp培训机构哪个比较好?国内10大热门PMP培训机构是哪些?

热门PMP培训机构推荐&#xff0c;PMP备考选择威班就是选择了高通过率 PMP热门培训机构方面我还是比较推荐威班的&#xff0c;当时选择的时候有人推荐我&#xff0c;也了解了很多&#xff0c;各种科普各种对比选择&#xff0c;最后还是选择了威班。经过体验他们的通过率比较靠谱…

Math类

java.lang.Math 提供了一系列静态方法用于科学计算&#xff0c;常用方法如下&#xff1a; abs 绝对值 acos&#xff0c;asin&#xff0c;atan&#xff0c;cos&#xff0c;sin&#xff0c;tan 三角函数 sqrt 平方根 pow(double a,double b) a的b次幂 max(double a,double b) 取大…

LiteFlow逻辑流引擎集成验证

本文将介绍开源逻辑流组件LiteFlow的架构、设计思想和适用场景&#xff0c;如何基于springboot集成LiteFlow&#xff0c;并验证DSL多种逻辑流程&#xff0c;以及逻辑流设计器的开发思路。 一、逻辑流解决什么问题 在每个公司的系统中&#xff0c;总有一些拥有复杂业务逻辑的系…

喜报 | 攸信技术再获殊荣,被授予厦门攸信智能制造系统研发中心

近日&#xff0c;厦门攸信信息技术有限公司凭借其卓越的科技创新实力和突出的研发成果&#xff0c;经过厦门市科学技术局的严格筛选与评审&#xff0c;三月份被正式授予“厦门攸信智能制造系统研发中心”的荣誉称号。 2023年12月&#xff0c;厦门市科学技术局积极响应《厦门市关…

2024年阿里云无影云电脑具体价格,99元一年起

2024年阿里云无影云电脑具体价格99元一年起&#xff0c;配置可选4核8G和8核16G&#xff0c;使用时长可选800小时和1800小时&#xff0c;目前有四款无影云电脑可以享受优惠价格&#xff0c;阿里云服务器网aliyunfuwuqi.com整理2024年无影云电脑详细配置和优惠价格表&#xff0c;…

20240322-1-协同过滤面试题

协同过滤面试题 1. 协同过滤推荐有哪些类型 基于用户(user-based)的协同过滤 基于用户(user-based)的协同过滤主要考虑的是用户和用户之间的相似度&#xff0c;只要找出相似用户喜欢的物品&#xff0c;并预测目标用户对对应物品的评分&#xff0c;就可以找到评分最高的若干个物…

VS Code 安装

VS Code 安装文档 一、下载 进入VS Code官网&#xff1a;https://code.visualstudio.com&#xff0c;点击 DownLoad for Windows下载windows版本 当然也可以点击旁边的箭头&#xff0c;下载Windows版本 或 Mac OS 版本 Stable&#xff1a;稳定版Insiders&#xff1a;内测版 …

算法系列--动态规划--背包问题(4)--完全背包拓展题目

&#x1f495;"这种低水平质量的攻击根本就不值得我躲&#xff01;"&#x1f495; 作者&#xff1a;Lvzi 文章主要内容&#xff1a;算法系列–动态规划–背包问题(4)–完全背包拓展题目 大家好,今天为大家带来的是算法系列--动态规划--背包问题(4)--完全背包拓展题目…

Codeforces Round 838 (Div. 2) D. GCD Queries

题目 思路&#xff1a; #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e6 5, inf 1e9, maxm 4e4 5; co…