当前位置: 首页 > java >正文

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

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

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 同步块对同⼀条线程来说是可重⼊的,不会出现⾃⼰把⾃⼰锁死的问题;

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

Java 中的 synchronized 是 可重⼊锁, 因此没有上⾯的问题.

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

在可重⼊锁的内部, 包含了 “线程持有者” 和 “计数器” 两个信息.
• 如果某个线程加锁的时候, 发现锁已经被⼈占⽤, 但是恰好占⽤的正是⾃⼰, 那么仍然可以继续获取到锁, 并让计数器⾃增.
• 解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)

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 放到一个 Testt 对象中. 通过上述 add 方法来进行修改,加锁的时候锁对象,写作 this

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

Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, ⼜没有任何加锁措施.
• ArrayList
• LinkedList
• HashMap
• TreeMap
• HashSet
• TreeSet
• StringBuilder
但是还有⼀些是线程安全的. 使⽤了⼀些锁机制来控制.
• Vector (不推荐使⽤)
• HashTable (不推荐使⽤)
• ConcurrentHashMap
• StringBuffer
在这里插入图片描述

StringBuffer 的核⼼⽅法都带有 synchronized .

还有的虽然没有加锁, 但是不涉及 “修改”, 仍然是线程安全的
• String

http://www.xdnf.cn/news/17784.html

相关文章:

  • 2025年,Javascript后端应该用 Bun、Node.js 还是 Deno?
  • MyBatis-Plus 分页失效问题解析:@Param 注解的影响与解决方案
  • “我店模式”:零售转型中的场景化突围
  • 万字长文全解析:五种主流归一化方法深入讲解(BN/LN/IN/GN/WN)
  • 资源查看-lspci命令
  • React useMemo 深度指南:原理、误区、实战与 2025 最佳实践
  • Linux网络性能调优终极指南:深度解析与实践
  • pt-online-schema-change 全解析:MySQL 表结构变更的安全之道
  • Jenkins(集群与流水线配置)
  • 神经网络的核心组件解析:从理论到实践
  • Qt字符串与数值相互转换
  • 蓝桥杯备赛 按键、LCD、定时器
  • 面试实战 问题二十七 java 使用1.8新特性,判断空
  • 3.Ansible编写和运行playbook
  • 基于 MybatisPlus 将百度天气数据存储至 PostgreSQL 数据库的实践
  • k8s中的微服务
  • HTTP 请求转发与重定向详解及其应用(含 Java 示例)
  • 【Golang】 Context.WithCancel 全面解析与实战指南
  • model层实现:
  • CompletableFuture介绍及使用方式
  • Java多线程进阶-从乐观锁到读写锁
  • 网络安全和基础设施安全局 (CISA) 表示微分段不再是可选的
  • 基于UniApp的新大陆物联网平台温湿度检测系统开发方案
  • 基于机器学习的自动驾驶汽车新型失效运行方法
  • FPGA的PS基础1
  • 中科米堆CASAIM汽车零部件三维扫描检测解决方案
  • 我的 LeetCode 日记:Day 36 - 动态规划,背包问题的千变万化
  • PyTorch神经网络工具箱(神经网络核心组件)
  • 副水箱水位传感器3825-00009介绍
  • ZED 2i相机调试