【多线程初阶】synchronized -监视器锁monitor lock

article/2025/6/9 1:03:47

文章目录

  • 🌅synchronized关键字
    • 🌊 synchronized 的互斥
    • 🌊 synchronized 的变种写法
      • 🏄‍♂️synchronized 修饰代码块 :明确指定锁哪个对象
      • 🏄‍♂️synchronized 修饰方法
    • 🌊 synchronized 的可重入性
      • 🏄‍♂️可重入锁实现原理
      • 🏄‍♂️JVM怎么区分{ } 是synchronized的{}

🌅synchronized关键字

monitor lock 是JVM中采用的一个术语,使用锁的过程中抛出一些异常,可能会看到,监视器锁这样的报错信息

🌊 synchronized 的互斥

synchronized 会起到互斥的效果,某个线程执行到某个对象的 synchronized 中时,其他线程如果也执行到同一个对象 synchronized就会阻塞等待

  • 进入 synchronized 修饰的代码块,相当于 加锁
  • 退出 synchronized 修饰的代码块,相当于 解锁
synchronized {	//进入代码块,加锁//执行一些要保护的逻辑
}				//退出代码块,解锁

synchronized 用的锁是存在Java对象里的
可以粗略理解成,每个对象在内存在存储的时候,都存有一块内存表示当前的"锁定状态(类似于卫生间的"有人/无人")
如果当前是"无人"状态,那么就可以使用,使用时需要设为"有人"状态
如果当前是"有人"状态,呢么其他人无法使用,只能排队

理解阻塞等待:
针对每一把锁,操作系统内部都维护了一个等待队列,当这个锁被某个线程占有的时候,其他线程尝试进行加锁,就加不上了,就会阻塞等待,一直等到之间的线程解锁之后,由操作系统唤醒一个新的线程,再来获取到这个锁
注意:

  • 上一个线程解锁之后,下一个线程并不是立即就能获取到锁,而是要靠操作系统来"唤醒",这也是操作系统线程调度的一部分工作
  • 假设由 A B C 三个线程,线程 A 先获取到锁,然后 B 尝试获取锁,然后 C再尝试获取锁,此时 B 和 C都在阻塞队列中排队等待,但是当 A 释放锁之后,虽然 B 比 C先来,但是B 不一定就能获取锁,而是和 C 重新竞争,并不遵守先来后到的规则

在这里插入图片描述
两个线程,针对同一个对象加锁,才会产生互斥效果(一个线程加上锁了,另一个线程就得阻塞等待,等到第一个线程释放锁,才有机会)

如果是不同的锁对象,此时不会有互斥效果,线程安全没问题,没有得到改变

在这里插入图片描述

public class Demo17 {private static int count = 0;public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() ->{for (int i = 0; i < 500000; i++) {synchronized (locker){count++;}}System.out.println(" t1 结束");});Thread t2 = new Thread(() ->{for (int i = 0; i < 500000; i++) {synchronized (locker){count++;}}System.out.println(" t2 结束");});}
}

分析一下上述代码的具体执行
在这里插入图片描述
Java中为啥使用 synchronized + 代码块的做法?而不是采用lock + unlock函数的方式来搭配呢?(其实在Java中也有 lock/unlock风格的锁,只不过一般很少使用)

Java采取 synchronized,就能确保,只要出了 } 一定能释放锁,无论因为 return 还是异常,无论里面调用了哪些其它代码,都可以确保unlock 操作执行到的

此时,其他语言也参考了Java这里的设定,C++提供了lock_guard机制,Python提供了with语句(上下文管理器),Go提供了defer关键字

本质上都是和synchronized一样的,都是出了代码块就能自动释放锁

🌊 synchronized 的变种写法

🏄‍♂️synchronized 修饰代码块 :明确指定锁哪个对象

  • 1.锁任意对象
public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() ->{synchronized (locker){count++;}});
  • 2.锁当前对象
public static void main(String[] args) {Thread t1 = new Thread(() ->{synchronized (this){}});

🏄‍♂️synchronized 修饰方法

  • 1.直接修饰普通方法 : 锁的SynchronziedDemo对象
  • 2.修饰静态方法 : 锁的SynchronziedDemo 类的对象
class Counter{private int count = 0;synchronized public void add(){count++;}public int get(){return count;}public synchronized  static void func(){synchronized (Counter.class){}}}
public class Demo18 {public static void main(String[] args) throws InterruptedException {Object locker = new Object();Counter counter = new Counter();Thread t1 = new Thread(() ->{for (int i = 0; i < 50000; i++) {counter.add();}});Thread t2 = new Thread(() ->{for (int i = 0; i < 50000; i++) {counter.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count="+counter.get());}
}

针对上述代码,其中:
在这里插入图片描述

🌊 synchronized 的可重入性

synchronized 同步对同一条线程来说是可重入的,不会出现自己把自己锁死的问题

理解"把自己锁死"
一个线程没有释放锁,然后又尝试再次加锁
// 第一次加锁,加锁成功
lock();
// 第二次加锁,锁已经被占用,阻塞等待
lock();
按照之前对于锁的设定,第二次加锁的时候,就会阻塞等待,直到第一次的锁被释放,才能获取到第二个锁,但是释放第一个锁也是由该线程来完成的,结果这个线程不干了,无法进行解锁操作,这时候就会死锁

Java中的 synchronized 是可重入锁,因此没有上面的问题

在这里插入图片描述
比如以下代码的使用:

class Counter2{private int count = 0;public void add(){synchronized (this) {count++;}}public int get(){return count;}}
public class Demo19 {public static void main(String[] args) throws InterruptedException {Counter2 counter2 = new Counter2();Thread t1 = new Thread(() ->{for (int i = 0; i < 50000; i++) {synchronized (counter2){counter2.add();}}});t1.start();t1.join();System.out.println("count = "+counter2.get());}
}

在这里插入图片描述
死锁是一个非常严重的bug,使代码执行到这一块之后,就卡住
为了解决上述问题,Java的 synchronized 就引入了可重入的概念

在这里插入图片描述
在这里插入图片描述

🏄‍♂️可重入锁实现原理

可重入锁的实现原理,关键在于让锁对象,内部保存,当前是哪个线程持有这把锁,后续有线程针对这个锁加锁的时候,对比一下,锁持有者的线程是否和当前加锁的线程是同一个

在这里插入图片描述

🏄‍♂️JVM怎么区分{ } 是synchronized的{}

{ }这个大括号,只是Java代码角度理解的,JVM看到的是字节码
java => .class 这个过程编译器已经处理了
JVM 执行 .class

Java代码中看到的是{ },字节码中,对应的是不同的指令 { 涉及到加锁指令 } 涉及到解锁指令 像是 if else while 他们的 { } 是不会被编译成,加锁解锁指令的~~


http://www.hkcw.cn/article/jKGHRhwggR.shtml

相关文章

C# 面向对象特性

面向对象编程的三大基本特性是&#xff1a;封装、继承和多态。下面将详细介绍这三大特性在C#中的体现方式。 封装 定义&#xff1a;把对象的数据和操作代码组合在同一个结构中&#xff0c;这就是对象的封装性。 体现方式&#xff1a; 使用访问修饰符控制成员的可见性 通过属…

[LitCTF 2024]浏览器也能套娃?

打开题目在线环境&#xff1a; 这里尝试填写一个网站回车之后是&#xff1a; 这里想到了ssrf漏洞&#xff1a;ssrf漏洞通常是由于应用程序在处理用户输入时缺乏严格的安全检查&#xff0c;错误地信任外部输入&#xff0c;或者使用的网络请求库配置不当等原因导致的。攻击者可…

明远智睿SSD2351开发板:视频监控领域的卓越之选

随着安全防范意识的提高&#xff0c;视频监控在各个领域得到了广泛应用。明远智睿SSD2351开发板凭借其出色的性能和特性&#xff0c;成为视频监控领域的卓越之选&#xff0c;为视频监控系统的升级和发展提供了有力支持。 SSD2351开发板的四核1.4GHz处理器在视频监控数据处理方面…

threejs加载外部三维模型(gltf)

1. 建模软件绘制3D场景(Blender) 这节课主要给大家科普一些三维模型创建、美术和程序员协作的相关问题。 三维建模软件作用 对于简单的立方体、球体等模型&#xff0c;你可以通过three.js的几何体相关API快速实现&#xff0c;不过复杂的模型&#xff0c;比如一辆轿车、一栋房…

【从零开始系列】Qwen2.5 Llama-Factory:开源语言大模型+训练平台——(超详细、最新版)一篇文章解决:环境搭建 => 微调训练 => 本地部署

目录 一、简介 1.Qwen2.5&#xff1a;开源模型 2. LLaMA-Factory&#xff1a;微调工具 二、环境搭建 1.Python和Pytorch版本 2.llamafactory项目克隆安装 3.其他重要库安装 三、模型微调 1.预训练模型下载 2.训练数据集 ①创建对话文本数据 ②配置dataset_info 3.配置文件与…

git 代码提交规范,feat,fix,chore都是什么意思?

写到前面 经常看到别人提交的代码记录里面包含一些feat、fix、chore等等&#xff0c;而我在提交时也不会区分什么&#xff0c;直接写下提交信息&#xff0c;今天就来看一下怎么个事&#xff0c;就拿 element-plus 举例来看一下 其实这么写是一种代码提交规范&#xff0c;当然…

Gemma 3模型:Google 开源新星,大语言模型未来探索

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《深度探秘&#xff1a;AI界的007》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、快速发展的AI世界&#xff1a;为何关注Gemma 3&#x…

2024.11最新Hexo+GitHub搭建个人博客

2024.11最新HexoGitHub搭建个人博客 一、Hexo介绍 Hexo 是一个快速、简洁且高效的博客框架&#xff0c;有丰富的主题和插件可供使用。 Hexo 使用 Markdown&#xff08;或其他标记语言&#xff09;解析文章&#xff0c;在几秒内&#xff0c;即可利用靓丽的主题生成静态网页。这…

完美解决 git 报错 “fatal: unable to access ‘https://github.com/.../.git‘: Recv failure Connection was rese

文章目录 方法一&#xff1a;取消代理设置方法二&#xff1a;设置系统代理结语 &#x1f389;欢迎来到Java学习路线专栏~探索Java中的静态变量与实例变量 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#xff1a;IT陈寒的博客&#x1f388;该系列文章专栏&#xf…

git 拉取Github时报错【 Recv failure: Connection was reset】

问题 当我们电脑能够正常访问Github时&#xff0c;但是git拉取代码出现 &#xff1a; Recv failure: Connection was reset原因 这是因为使用了特殊上网方法&#xff0c;电脑能够正常访问&#xff0c;但是git通过底层访问需要配置代理才能正常访问 解决办法 配置方法如下&…

【PostgreSQL系列】PostgreSQL性能优化

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

如何使用 TortoiseGit 将本地代码回退到指定版本

如何使用 TortoiseGit 将本地代码回退到指定版本 在使用 Git 进行版本控制时&#xff0c;我们可能会遇到需要回退到某个旧版本的情况&#xff0c;比如发现最近的修改引入了问题&#xff0c;或者需要恢复到某个特定的稳定状态。TortoiseGit 是一款非常流行的 Git 图形化工具&am…

航空安全警钟须长鸣 充电宝冒烟再敲警钟

5月31日,CZ6850杭州飞往深圳的航班上,一名旅客携带的相机电池和充电宝突然冒烟。乘务组迅速采取措施,排除了安全风险。为确保安全,机组决定立即返航,航班在起飞15分钟后安全降落。这次事件虽然没有造成人员伤亡,但再次提醒人们航空安全的重要性。任何微小的安全隐患都可能…

玩客云 OEC/OECT 笔记

外观 内部 PCB正面 PCB背面 PCB背面 RK3566 1Gbps PHY 配置 OEC 和 OECT(OEC-turbo) 都是基于瑞芯微 RK3566/RK3568 的网络盒子, 没有HDMI输入输出. 硬件上 OEC 和 OECT 是一样的, 唯一的区别是内存, OEC 内存 2GB 而OECT 内存是 4GB. 产品OECOEC-turboCPURK3566…

InfluxDB 高级函数详解:DERIVATIVE、INTEGRAL、SPREAD、HISTOGRAM 与 DIFFERENCE

在时序数据分析中&#xff0c;除了基础的聚合函数&#xff08;如 MEAN、SUM&#xff09;&#xff0c;InfluxDB 还提供了一系列专门针对时间序列特性的高级函数。这些函数能帮助我们挖掘数据的变化趋势、波动特征和分布规律。下面我们将逐一解析五个关键函数&#xff1a;DERIVAT…

华为OD机试真题—— 最少数量线段覆盖/多线段数据压缩(2025A卷:100分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 A卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《最少数量线段覆盖/多线段数…

古代小孩哥怎么过六一 绿意中撒欢踢毽子

今天是六一儿童节,让我们一起看看古代的孩子们在这一天会玩些什么。古代的童趣VCR展示了高能量小孩哥的日常。他们在绿意盎然的环境中尽情撒欢,青翠的柳荫、碧绿的草地,还有亲密的玩伴。孩子们选择组团踢毽子,只见小孩哥眼神专注,动作轻快,毽子跃起时衣角也随之飘动。旁边…

有捏捏玩具甲醛超标40多倍 安全问题引热议

近日,拥有百万粉丝的捏捏玩具博主“有只猫叫小朋友”在社交平台上发布癌症诊断书,并表示暂停更新。这一举动引发了关于捏捏玩具安全性的讨论。有网友留言称,自己和孩子玩过捏捏玩具后出现了头疼、嗓子疼的情况。捏捏玩具是一种流行的硅胶材质慢回弹类解压玩具,外形多为软萌…

宇树机器狗go2添加3d雷达(下)添加velodyne系列雷达

0.前言 上一篇文章教大家如何在宇树机器狗go2的仿真环境中添加3d雷达livox mid360&#xff08;宇树机器狗go2 添加3d雷达&#xff08;上&#xff09;添加livox系列雷达&#xff09;&#xff0c;本期文章会教大家添加lvelodyne的系列雷达&#xff0c;是添加3d雷达的下期。宇树机…

美国终止艾滋病疫苗研发项目 转向现有方法消除艾滋病

特朗普政府终止了一项2.58亿美元的项目,对艾滋病疫苗研发工作造成了沉重打击。一位不愿透露姓名且未经授权发言的高级官员表示,美国国立卫生研究院计划将关注点转向利用现有方法消除艾滋病,并暂停了莫德纳公司研发的一项艾滋病疫苗临床试验。公共卫生专家指出,这些削减措施…