synchronized 关键字 - 监视器锁 monitor lock

  • 5. synchronized 关键字 - 监视器锁 monitor lock
    • 5.1 synchronized 的特性
    • 5.2 synchronized 使⽤⽰例
    • 5.3 Java 标准库中的线程安全类

本节⽬标
• 掌握 synchronized关键字

5. synchronized 关键字 - 监视器锁 monitor lock

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

5.1 synchronized 的特性

(1) 互斥
synchronized 会起到互斥效果, 某个线程执⾏到某个对象的 synchronized 中时, 其他线程如果也执⾏到同⼀个对象 synchronized 就会阻塞等待.
• 进⼊ synchronized 修饰的代码块, 相当于 加锁
• 退出 synchronized 修饰的代码块, 相当于 解锁
在这里插入图片描述

synchronized用的锁是存在Java对象头⾥的
可以粗略理解成, 每个对象在内存中存储的时候, 都存有⼀块内存表⽰当前的 “锁定” 状态(类似于厕所的 “有⼈/⽆⼈”).
如果当前是 “⽆⼈” 状态, 那么就可以使⽤, 使⽤时需要设为 “有⼈” 状态.
如果当前是 “有⼈” 状态, 那么其他⼈⽆法使⽤, 只能排队
在这里插入图片描述

理解 “阻塞等待”.
针对每⼀把锁, 操作系统内部都维护了⼀个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝试进⾏加锁, 就加不上了, 就会阻塞等待, ⼀直等到之前的线程解锁之后, 由操作系统唤醒⼀个新的线程,再来获取到这个锁.
注意:
• 上⼀个线程解锁之后, 下⼀个线程并不是⽴即就能获取到锁. ⽽是要靠操作系统来 “唤醒”. 这也就是操作系统线程调度的⼀部分⼯作.
• 假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B 和 C都在阻塞队列中排队等待。 但是当 A 释放锁之后, 虽然 B ⽐ C 先来的, 但是 B 不⼀定就能获取到锁,⽽是和 C 重新竞争, 并不遵守先来后到的规则.

锁, 本质上也是操作系统提供的功能,内核提供的功能 =>通过 api 给应用程序了。java (VM)对于这样的系统 api 又进行了封装.
synchronized 是调用 系统的 api 进行加锁。系统 api 本质上是靠 cpu 上的特定指令完成加锁
(2)可重⼊
synchronized 加锁的效果,也可以称为"互斥性。synchronized 还有一些其他特性:

理解 “把⾃⼰锁死”
⼀个线程没有释放锁, 然后⼜尝试再次加锁.
// 第⼀次加锁, 加锁成功
lock();
// 第⼆次加锁, 锁已经被占⽤, 阻塞等待.
lock();
按照之前对于锁的设定, 第⼆次加锁的时候, 就会阻塞等待. 直到第⼀次的锁被释放, 才能获取到第⼆个锁. 但是释放第⼀个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想⼲了, 也就⽆法进⾏解锁操作. 这时候就会 死锁.
在这里插入图片描述
这样的锁称为 不可重⼊锁

for (int i = 0; i < 50000; i++) {synchronized (locker) {synchronized (locker) {count++;}}
}

看起来是两次一样的加锁,没有必要。但是实际上开发中,很容易写出这样的代码的。
在这里插入图片描述
一旦方法调用的层次比较深,就搞不好容易出现这样的情况
在这里插入图片描述
要想解除阻塞,需要往下执行才可以,要想往下执行就需要等到第一次的锁被释放,这样的问题,就称为"死锁”。
这样的代码在 Java 中其实是不会死锁的!!! 为了避免程序猿粗心大意搞出死锁!java引入了"可重入机制",Java 中的 synchronized 是 可重⼊锁, 因此没有上⾯的问题。

最外层“ { ”真正加锁
最外层“ }” 真正解锁
站在 JVM 的视角,看到多个}需要执行,JVM 如何知道哪个}是真正解锁的那个??
先引入一个变量,计数器(0),每次触发{的时候,把计数器++,每次触发 } 的时候,把计数器 - -,当计数器 - - 为 0 的时候, 就是真正需要解锁的时候~

在可重⼊锁的内部, 包含了 “线程持有者” 和 “计数器” 两个信息:(1)如果某个线程加锁的时候, 发现锁已经被⼈占⽤, 但是恰好占⽤的正是⾃⼰, 那么仍然可以继续获取到锁, 并让计数器⾃增.(2)解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)
死锁是面试中考察的重点,也是工作中,多线程开发中非常核心的注意事项~~
若面试官的问题:
如何自己实现一个可重入锁?
1.在锁内部记录当前是哪个线程持有的锁,后续每次加锁,都进行判定
2.通过计数器,记录当前加锁的次数,从而确定何时真正进行解锁,

死锁(死锁的进一步讨论)
“死锁”是多线程代码中的一类经典问题, 加锁是能解决线程安全问题,但是如果加锁方式不当,就可能产生死锁!!
死锁同样也是经典面试题!!
死锁的三种典型场景
场景1. 一个线程, 一把锁.
刚才说情况 如果锁是不可重入锁,并且一个线程针对一个锁对象,连续加锁两次,就会出现死锁(钥匙锁屋里了)
通过引入可重入锁,问题就迎刃而解了
场景2. 两个线程,两把锁
两个线程,两把锁,每个线程获取到一把锁之后,尝试获取对方的锁。线程1 获取到 锁A,线程2 获取到 锁B,接下来1 尝试获取 B,2 尝试获取 A,就同样出现死锁了!!!(屋钥匙锁车里了,车钥匙锁屋里了)
在这里插入图片描述
如果不加 sleep, 很可能 t1 一口气就把 locker1 和 locker2 都拿到了.这个时候,t2 还没开动呢~ 自然无法构成死锁.
经典面试题:让你手写一个出现死锁的代码:
C++方向,代码就好写,直接加锁两次就行了
Java 方向,就得通过上述代码,两个线程两把锁,精确控制好加锁的顺序在这里插入图片描述
这里也就需要让我们知道,如果遇到死锁问题,就可以通过上述调用栈+状态进行定位了
场景3. N 个线程 M 把锁
一个经典的模型,哲学家就餐问题(学校的操作系统课上,也会有这个东西)在这里插入图片描述
死锁,非常严重的问题~~ 属于程序中最严重的一类 bug !!!
一旦出现死锁,线程就"卡住了"无法继续工作,一个进程中的线程个数,就那么多。更可怕的是,死锁这种bug, 往往都是概率 出现,测试的时候怎么测试都没事,一发布就出问题,发布了也没问题,等到夜深人静,大家都睡着,突然给你整出点问题!比 bug 更可怕的是,“概率性出现的 bug”。虽然概率小,但是我们也需要重视!! 假设上述问题的 概率是 万分之一,同样是需要我们处理的,当时阿里这边的服务器每天的访问量是 3亿次,每天就有 3万个用户,触发了这个 bug!
如何避免死锁问题?
教科书上经典的,死锁的四个必要条件 !!!(下列四个条件,要求大家背下来!!面试经典问题!!)必要条件: 缺一不可!任何一个死锁的场景,都必须同时具备上述四点,只要缺少一个,都不会构成死锁。
1.锁具有互斥特性.
一个线程拿到锁之后,其他线程就得阻塞等待(锁最基本的特性.,不太好破坏)
2.锁不可抢占(不可被剥夺)
一个线程拿到锁之后,除非他自己主动释放锁,否则别人抢不走~~(也是锁最基本的特性.,也不好破坏)
3.请求和保持
一个线程拿到一把锁之后,不释放这个锁的前提下,再尝试获取其他锁。(如果先放下左手的筷子,再拿右手的筷子, 就不会构成死锁! 代码中加锁的时候,不要去“嵌套”。这种做法, 通用性, 不够的。 嵌套,很难避免:有些情况下,确实是需要拿到多个锁, 再进行某个操作的.)
4.循环等待. 多个线程获取多个锁的过程中,出现了循环等待。A 等待 B, B 也等待 A 或者 A 等待 B,B 等待 C, C 等待 A。(约定好加锁的顺序(比如按照编号从小到大的顺序),就可以破除循环等待了)
在这里插入图片描述解决死锁问题,核心思路, 破坏上述的必要条件,只要能破坏一个,就搞定!!上述破坏3 4两种 是开发中比较实用的方法,还有一些其他方案,也能解决死锁问题.但引入加锁顺序的规则(普适性高, 方案容易落地)
死锁的小结
在这里插入图片描述

死锁这里非常重要的,时面试高频的问题。
"谈谈你对于死锁的理解”
死锁:
1.死锁是啥
2.死锁的三个场景
3. 死锁的危害
4.死锁的必要条件, 如何解决死锁

5.2 synchronized 使⽤⽰例

synchronized 本质上要修改指定对象的 “对象头”. 从使⽤⻆度来看, synchronized 也势必要搭配⼀个具体的对象来使⽤.
(1) 修饰代码块: 明确指定锁哪个对象.
锁任意对象

public class SynchronizedDemo {private Object locker = new Object();public void method() {synchronized (locker) {}}
}

锁当前对象

public class SynchronizedDemo {public void method() {synchronized (this) {}}
}

(2) 直接修饰普通⽅法: 锁的 SynchronizedDemo 对象

public class SynchronizedDemo {public synchronized void methond() {}
}

修饰一个普通方法,就可以省略"锁对象。在这里插入图片描述
等价于:
在这里插入图片描述
(3) 修饰静态⽅法: 锁的 SynchronizedDemo 类的对象

public class SynchronizedDemo {public synchronized static void method() {}
}

synchronized 修饰普通方法, 相当于给 this 加锁 (锁对象 this)
synchronized 修饰静态方法,相当于给类对象加锁

我们重点要理解,synchronized 锁的是什么.
两个线程竞争同⼀把锁, 才会产⽣阻塞等待.
两个线程分别尝试获取两把不同的锁, 不会产⽣竞争.
在这里插入图片描述

  1. 如果我一个线程加锁,一个线程不加锁,是否会存在线程安全问题?
    就不会出现锁竞争了!!!会存在线程安全问题
  2. 如果两个线程,针对不同的对象加锁呢?
    也会存在线程安全问题
    在一个程序中,锁,不一定只有一把。一个厕所,可能有多个坑位是一样的。每个坑位都有一个锁,如果你两个线程,针对不同的坑位加锁,不会产生互斥的(也称为 锁竞争/锁冲突)。只有是针对同一个坑位加锁,才有互斥。
    代码中,可以创建出多个锁。具体写代码的时候,想搞几个锁,就搞几个。只有多个线程竞争同一把锁,才会产生互斥,针对不同的锁,则不会。
  3. 针对加锁操作的一些混淆的理解
    把 count 放到一个 Test.t 对象中. 通过上述 add 方法来进行修改,加锁的时候锁对象,写作 this
    在这里插入图片描述
    synchronized (Test.class){ } 获取类对象 :
    在这里插入图片描述
    在这里插入图片描述
    在 java 代码中就可以通过类名.class 的方式拿到这个类对象。反射 api 就是从上述对象中获取信息的。
    一个 java 进程中, 某个类,只能有唯一一个类对象
    在这里插入图片描述
    在这里插入图片描述
    synchronized 的变种写法,可以使用 synchronized 修饰方法 。synchronized (this),也可以等价把 synchronized 加到方法上。在这里插入图片描述
    在这里插入图片描述
    方法中还有一个特殊的情况:
    static 修饰的方法,不存在 this.(static 修饰的方法,也叫做"类方法,不是针对"实例"的方法,而是针对类的,在这个方法中, 没有 this.) 此时, synchronized 修饰 static 方法, 相当于针对类对象加锁
    在这里插入图片描述

其他编程语言中,加锁解锁, 都是单独的方法。对比其他语言,java 的加锁操作风格是独树一帜的。Java 中为啥使用 synchronized + 代码块 做法?而不是采用 lock + unlock 函数的方式来搭配呢?
像 C++ 这种写法, 就可能会,忘记调用 unlock(unlock 没有执行到),如果忘记调用 unlock 其他线程都无法获取到这个锁, 产生严重的 bug!!
Java 采取的 synchronized, 就能确保, 只要出了 } 一定能释放锁. 无论因为 return 还是因为 异常,无论里面调用了哪些其他代码,都是可以确保 解锁 操作执行到的.在这里插入图片描述
只要我写了 lock,就会立即加上 unlock 。这种说法,纯纯的,大猪蹄子行为,你给妹子保证,我这辈子只爱你一个,永远不会变心。就算你非常细心,能够确保每个 条件都加 unlock,但是你不能保证,你们组新来的实习生,也能做到这一点(各位同学们, 你们很可能就是这个实习生)
(其实在 Java 中,也有 lock/unlock 风格的锁, 一般很少使用)
在这里插入图片描述
但是c++没有 finally ,只能靠程序猿人工来保证了~~(很有可能,java 程序员代码早早写完,也没啥 bug, 下班回去打游戏了,C++ 程序员还在苦苦寻找哪里没有释放锁)。但是更新版本的 C++ 引入了 lock quard (守卫)这个东西,可以起到类似于 synchronized,代码块结束之后,就能自动释放锁。

5.3 Java 标准库中的线程安全类

  1. Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, ⼜没有任何加锁措施.(把加锁决策交给程序员)
    线程不安全.多个线程,尝试修改同一个上述的对象,就很容易出现问题!! 而不是 100%,也可能你这个代码写出来之后,是没问题的,具体代码具体分析(多线程代码,稍微变换一点,就可能有不一样的结果)
    • ArrayList
    • LinkedList
    • HashMap
    • TreeMap
    • HashSet
    • TreeSet
    • StringBuilder
  2. 但是还有⼀些是线程安全的. 使⽤了⼀些锁机制来控制.
    自带了锁, 在多线程环境下时候,能好点。也不是 100% 不出问题!! 只是概率比上面小很多,具体代码具体分析!!!(多线程代码,稍微变换一点, 就可能有不一样的结果)
    像Vector,HashTable,StringBuffer 这几个类都属于是 标准库 即将弃用,不推荐使用,暂时还留着(保持和老的代码兼容)。这个时候,新的代码就不要用了,未来某一天新版本的 jdk,就把这些内容给删了。
    • Vector (不推荐使⽤)
    • HashTable (不推荐使⽤)
    Java 早起,各位 Java 大佬还不够成熟时,引入的设定。现在的话这些设定已经被推翻了,不建议使用了.
    • ConcurrentHashMap
    相比于 HashTable 来说,高度优化的版本(后续详细分析)
    • StringBuffer
    StringBuffer 的核⼼⽅法都带有 synchronized .在这里插入图片描述
    一旦代码中, 使用了锁,意味着代码可能会因为锁的竞争,产生阻塞=>程序的执行效率大打折扣.
    一定要思考清楚, 这个地方是否确食需要锁,不需要的时候不要乱加.
    线程阻塞 =>从 cpu 上调度走,啥时候能调度回来继续执行???不好说了~~ 沧海桑田
  3. 还有的虽然没有加锁, 但是不涉及 “修改”, 仍然是线程安全的
    • String

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

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

相关文章

Java多线程:从基础到实战

引言多线程是Java并发编程的核心技术之一&#xff0c;广泛应用于服务器开发、数据处理、实时系统等领域。通过多线程&#xff0c;程序可以充分利用CPU资源&#xff0c;提高执行效率&#xff0c;同时处理多个任务。本文将从多线程的基本概念、实现方式、线程状态、同步与通信到常…

list集合可以一边遍历一遍修改元素吗?

今天看来一下Java中list集合部分的八股&#xff0c;发现了一个以前没注意过的问题&#xff0c;记录一下list可以一边遍历一边修改元素吗&#xff1f;答&#xff1a;在 Java 中&#xff0c;List在遍历过程中是否可以修改元素取决于遍历方式和具体的List实现类。①&#xff1a;对…

Infusing fine-grained visual knowledge to Vision-Language Models

Infusing fine-grained visual knowledge to Vision-Language Models Authors: Nikolaos-Antonios Ypsilantis, Kaifeng Chen, Andr Araujo, Ondřej Chum Deep-Dive Summary: 视觉-语言模型中注入细粒度视觉知识 摘要 大规模对比预训练产生了强大的视觉-语言模型&#xf…

RK3576赋能无人机巡检:多路视频+AI识别引领智能化变革

随着工业巡检任务的复杂度不断提升&#xff0c;无人机逐渐取代传统人工&#xff0c;成为电力、能源、林业、农业等行业的“高空作业主力”。然而&#xff0c;巡检并非简单的拍摄和回放&#xff0c;它要求无人机实时采集多路画面、快速分析异常&#xff0c;并稳定回传数据。这对…

ollama Modelfile 文件生成

输入 根据如下TEMPLATE和params写一个modelfile文件&#xff0c;TEMPLATE为&#xff1a;{{- $lastUserIdx : -1 -}} {{- range $idx, $msg : .Messages -}} {{- if eq $msg.Role “user” }}{{ $lastUserIdx $idx }}{{ end -}} {{- end }} {{- if or .System .Tools }}<|i…

关联规则挖掘2:FP-growth算法(Frequent Pattern Growth,频繁模式增长)

目录 一、核心思想&#xff1a;一个形象的比喻 二、核心思想的具体拆解 步骤一&#xff1a;构建FP-tree&#xff08;频繁模式树&#xff09; 步骤二&#xff1a;从FP-tree中挖掘频繁项集 为什么这很高效&#xff1f; 三、总结 核心思想与优势 适用场景与缺点 四、例题…

在IDEA中DEBUG调试时查看MyBatis-Plus动态生成的SQL语句

在IDEA中DEBUG调试时查看MyBatis-Plus动态生成的SQL语句前言&#xff1a;动态SQL调试的痛与解决方案一、准备工作&#xff1a;调试前的检查清单二、基础方法&#xff1a;SqlSessionTemplate断点调试步骤1&#xff1a;定位SqlSessionTemplate类步骤2&#xff1a;在invoke方法上设…

Linux 文本处理三剑客:awk、grep、sed 完全指南

Linux 文本处理三剑客&#xff1a;awk、grep、sed 完全指南 1. 概述 Linux 系统提供了三个强大的文本处理工具&#xff1a;awk、grep 和 sed&#xff0c;它们各有所长&#xff0c;结合使用可以高效地处理文本数据。 awk&#xff1a;擅长文本分析和格式化输出&#xff0c;是一…

pyecharts可视化图表组合组件_Grid:打造专业数据仪表盘

pyecharts可视化图表组合组件_Grid&#xff1a;打造专业数据仪表盘 目录pyecharts可视化图表组合组件_Grid&#xff1a;打造专业数据仪表盘引言图表1&#xff1a;Grid-Overlap-多X/Y轴示例代码解析1. 图表创建2. 多轴配置3. 图表重叠4. Grid布局效果与应用图表2&#xff1a;Gri…

【电气工程学习】

三极管中&#xff1a;集电极C,基极B&#xff0c;发射极E接线&#xff1a;棕正蓝负黑信号NPN开关输出的是我们的0V,也叫低电平PNP开关输出的是24V,也就是高电平&#xff08;NPN开关导通时&#xff0c;相当于把输出端“拉”到0V&#xff08;低电平&#xff09;&#xff0c;称为“…

【嵌入式】CAN通信

CAN 总线最初由博世于1980年代为汽车行业开发&#xff0c;能够简化复杂的布线网络&#xff0c;还确保可靠和安全的数据传输。 1.CAN技术解释 CAN网络中的每个节点&#xff0c;都是平等的&#xff0c;没有主次之分&#xff0c;这一点和SPI和I2C不同。每个节点都可以在需要的时…

Apache ShenYu网关与Nacos的关联及如何配合使用

Apache ShenYu 网关与 Nacos 之间的关系可以概括为 “协作互补”:Nacos 作为 服务注册与配置中心,为 ShenYu 提供动态的服务发现和配置管理能力,而 ShenYu 作为 流量网关,依赖 Nacos 实现路由信息的动态更新和实时生效。以下是详细解析: 1. 核心关系图解 拉取服务列表/路…

【CPP】一个CPP的Library(libXXXcore)和测试程序XXX_main的Demo

一个CPP的Library和测试程序Demo 1. 思路描述 目录结构 总控CMakeList.txt文件 2. Library代码实现 2.1 XXXLib.hpp文件(对外的接口定义文件)和XXXLib.cpp文件 2.1.1 XXXLib.hpp文件 2.1.2 XXXLib.cpp文件 2.2 CXXXLibApi.hpp文件和CXXXLibApi.cpp文件(内部的API基类) 2.2.1 CX…

【YashanDB认证】学习YashanDB的探索之路:从入门到实践

在国产数据库蓬勃发展的浪潮中&#xff0c;选择了YashanDB作为技术学习的切入点。这不仅让我深入了解了数据库的核心技术&#xff0c;也让我深刻体会到国产数据库在性能、可靠性和生态适配上的创新价值。以下是我在学习YashanDB过程中的经验与感悟。 一、YashanDB基础介绍 Ya…

element UI 和 element plus 在组件上有哪些不同

Element UI 和 Element Plus 都是基于 Vue 的桌面端 UI 组件库&#xff0c;由同一团队&#xff08;饿了么前端团队&#xff09;开发和维护。Element Plus 是 Element UI 的升级版&#xff0c;专为 Vue 3 设计&#xff0c;而 Element UI 仅支持 Vue 2。以下是它们在组件层面的主…

【3D重建技术】如何基于遥感图像和DEM等数据进行城市级高精度三维重建?

城市级高精度三维重建是融合多源空间数据&#xff08;遥感图像、DEM、GIS矢量等&#xff09;、计算机视觉与地理信息处理技术的复杂过程&#xff0c;核心目标是构建包含“地形地物&#xff08;建筑、道路、植被等&#xff09;”的真实、高精度三维场景。其流程可分为数据准备、…

【unitrix数间混合计算】3.4 无符号小数部分标记trait(bin_unsigned.rs)

一、源码 这段代码定义了一个类型级二进制小数系统&#xff0c;用于在编译时表示和验证二进制小数部分的有效性。 use crate::number::{F0, BFrac, Bit};/// 标记合法的二进制小数部分类型 pub trait BinFrac: Copy Default static {}// 空小数部分&#xff08;表示值为0&…

从一次 DDoS 的“死亡回放”看现代攻击链的进化

本文记录的是作者上周在测试环境真实踩到的坑。为了让读者能复现并亲手体验防御思路&#xff0c;文末给出了一份最小可运行的 Go 脚本&#xff0c;支持本地压测 日志回放&#xff0c;方便对比加防护前后的差异。攻击现场还原 周一凌晨 2:14&#xff0c;监控群里突然弹出告警&a…

LeetCode热题100--101. 对称二叉树--简单

1. 题目 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a;输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a;输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 2. 题解 /*** Definition for…

Pub/Sub是什么意思

Pub/Sub&#xff08;发布/订阅模式&#xff09;​​ 是一种异步消息通信范式&#xff0c;用于分布式系统中不同组件之间的解耦通信。它的核心思想是将消息的发送方&#xff08;发布者&#xff09;​​ 和接收方&#xff08;订阅者&#xff09;​​ 分离&#xff0c;通过一个中间…