【JavaEE】多线程 (2) --线程安全

目录

1. 观察线程不安全

2. 线程安全的概念

3. 线程不安全的原因

4. 解决之前的线程不安全问题

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

5.1 synchronized 的特性

5.2 synchronized 使⽤⽰例


1. 观察线程不安全

package thread;
public class ThreadDemo19 {private static int count = 0;public static void main(String[] args) throws InterruptedException {//创建两个线程,每个线程都针对上面的count变量循环自增5w次Thread t1 = new Thread(()-> {for(int i = 0; i<50000; i++) {count++;}});Thread t2 = new Thread(()-> {for(int i = 0; i<50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}

执行上面的代码,我们发现结果并不是100000, 并且多次运行, 每次的结果都有所不同: 

这就是线程不安全的一个例子. 

2. 线程安全的概念

想给出⼀个线程安全的确切定义是复杂的,但我们可以这样认为:

如果多线程环境下代码运⾏的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

3. 线程不安全的原因

线程调度是随机的

  •  这是线程安全问题的罪魁祸⾸
  • 随机调度使⼀个程序在多线程环境下, 执⾏顺序存在很多的变数.
  • 程序猿必须保证在任意执⾏顺序下 , 代码都能正常⼯作.

修改共享数据

多个线程修改同⼀个变量

上⾯的线程不安全的代码中, 涉及到多个线程针对 count 变量进⾏修改. 此时这个 count 是⼀个多个线程都能访问到的 "共享数据"

原⼦性

什么是原⼦性

我们把⼀段代码想象成⼀个房间,每个线程就是要进⼊这个房间的⼈。如果没有任何机制保证,A进⼊ 房间之后,还没有出来;B 是不是也可以进⼊房间,打断 A 在房间⾥的隐私。这个就是不具备原⼦性的。

那我们应该如何解决这个问题呢?是不是只要给房间加⼀把锁,A 进去就把⻔锁上,其他⼈是不是就进不来了。这样就保证了这段代码的原⼦性了。 有时也把这个现象叫做同步互斥,表⽰操作是互相排斥的。

⼀条 java 语句不⼀定是原⼦的,也不⼀定只是⼀条指令

⽐如刚才我们看到的 count++,其实是由三步操作组成的:

1. 从内存把数据读到 CPU

2. 进⾏数据更新

3. 把数据写回到 CPU

 不保证原⼦性会给多线程带来什么问题

如果⼀个线程正在对⼀个变量操作,中途其他线程插⼊进来了,如果这个操作被打断了,结果就可能是错误的。

这点也和线程的抢占式调度密切相关. 如果线程不是 "抢占" 的, 就算没有原⼦性, 也问题不⼤.

可⻅性

可⻅性指, ⼀个线程对共享变量值的修改,能够及时地被其他线程看到

4. 解决之前的线程不安全问题

使用 synchronized 关键字将一条指令的多个操作, 打包成一个原子的操作.

下面是使用 synchronized 来解决上面代码的问题:

如果两个线程, 针对不同的对象加锁, 也会存在线程安全问题.

如果一个线程加锁, 一个线程不加锁, 是否会存在线程安全问题?

针对加锁操作的一些混淆理解

把count 放到一个Test t对象中, 通过类方法add 来进行修改, 加锁的时候锁对象写作 this

package thread;
class Test {public int count = 0;public void add() {synchronized (this) {count++;}}
}
public class ThreadDemo20 {public static void main(String[] args) throws InterruptedException {Test t = new Test();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {t.add();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {t.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + t.count);}
}

也可以使用类对象: 

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 重新竞争, 并不遵守先来后到的规则.

2) 可重⼊

synchronized 同步块对同⼀条线程来说是可重⼊的,不会出现⾃⼰把⾃⼰锁死的问题;

理解 "把⾃⼰锁死"

⼀个线程没有释放锁, 然后⼜尝试再次加锁.

// 第⼀次加锁, 加锁成功

lock();

// 第⼆次加锁, 锁已经被占⽤, 阻塞等待.

lock();

按照之前对于锁的设定, 第⼆次加锁的时候, 就会阻塞等待. 直到第⼀次的锁被释放, 才能获取到第⼆ 个锁. 但是释放第⼀个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想⼲了, 也就⽆法进 ⾏解锁操作. 这时候就会 死锁

这样的锁称为 不可重⼊锁.

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

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 锁的是什么. 两个线程竞争同⼀把锁, 才会产⽣阻塞等待.

两个线程分别尝试获取两把不同的锁, 不会产⽣竞争

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

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

相关文章

无公网IP下,如何实现公网远程访问MongoDB文件数据库

文章目录 前言1. 安装数据库2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射2.3 测试随机公网地址远程连接 3. 配置固定TCP端口地址3.1 保留一个固定的公网TCP端口地址3.2 配置固定公网TCP端口地址3.3 测试固定地址公网远程访问 前言 MongoDB是一个基于分布式文件存储的数…

抖音本地生活服务商申请条件

抖音的本地生活服务商目前有两种&#xff0c;一种是可以做全国的服务商&#xff0c;我们一般叫抖音本地生活服务商&#xff0c;一种是区域优待服务商&#xff0c;也就是后面出来的服务商&#xff0c;这两种服务商的申请方式大同小异。 相同的地方就是都需要给平台交保证金。抖…

网站监控有什么作用?

科技改变生活&#xff0c;科技的发展让我们的生活越来越精彩丰富&#xff0c;数据中心机房监控系统也可以称为“自我监控系统”&#xff0c;主要是针对机房所有的设备及环境进行集中监控和管理的&#xff0c;其监控对象构成机房的各个子系统&#xff1a;动力系统、环境系统、消…

CV计算机视觉每日开源代码Paper with code速览-2023.11.23

点击CV计算机视觉&#xff0c;关注更多CV干货 论文已打包&#xff0c;点击进入—>下载界面 点击加入—>CV计算机视觉交流群 1.【基础网络架构&#xff1a;Transformer】White-Box Transformers via Sparse Rate Reduction: Compression Is All There Is? 论文地址&am…

【Java】文件I/O-字节流转换成字符流

上文中我们讲了Reader&#xff0c;Writer&#xff0c;InputStream&#xff0c;OutputStream这四种流的基本用法&#x1f522; 【Java】文件I/O-文件内容操作-输入输出流-Reader/Writer/InputStream/OutputStream四种流 其中InputStream和OutputStream两个类涉及到的都是byte&…

rabbitMQ对消息不可达处理-备份交换机/备份队列

生产者发送消息&#xff0c;在消息不可达指定队列时&#xff0c;可以借助扇出类型交换机&#xff08;之前写过消息回退的处理方案&#xff0c;扇出交换机处理的方案优先级高于消息回退&#xff09;处理不可达消息&#xff0c;然后放置一个备份队列&#xff0c;供消费者处理不可…

IP地址的地理位置:固定性与动态性的平衡

IP地址的地理位置是网络通信中一个重要的元素&#xff0c;常被用于定位设备和用户。然而&#xff0c;很多人好奇&#xff0c;IP地址的地理位置是否会发生变化&#xff1f;本文将深入讨论IP地址地理位置的固定性与动态性之间的平衡&#xff0c;以及造成这种变化的因素。 1. IP地…

DevEco Studio设置每次进入 是否自动进入上一次的项目

首先 我们第一次创建项目 并不是这个界面 如果我们想在这个界面创建项目的话 可以 点击左上角 File 下的 New 下的 Create Project 这里 我们可以点击左上角 File 选择下面的 Settings… 这个界面就有非常多的配置 然后 我们选择到下图操作的位置 这里有一个Reopen projects…

MySQL进阶知识:锁

目录 前言 全局锁 表级锁 表锁 元数据锁&#xff08;MDL&#xff09; 意向锁 行级锁 行锁 行锁演示 间隙锁/临界锁 演示 前言 MySQL中的锁&#xff0c;按照锁的粒度分&#xff0c;分为以下三类 全局锁&#xff1a;锁定数据库中的所有表。表级锁&#xff1a;每次操…

民安智库(第三方市场调查公司):专业调研引领某月饼生产商企业发展

在中国的传统佳节中&#xff0c;月饼是一种重要的节日食品&#xff0c;也是送礼的首选。某月饼生产商一直以来以其高品质、独特口味的月饼而备受消费者喜爱。为了更好地了解消费者对产品的满意度&#xff0c;该月饼生产商决定委托民安智库&#xff08;湖北知名满意度测评公司&a…

el-row错位问题解决

<el-row type"flex" style"flex-wrap:wrap">

yolov8 原木识别模型

一、模型介绍 模型基于 yolov8数据集采用SKU-110k&#xff0c;这数据集太大了十几个 G&#xff0c;所以只训练了 10 轮左右就拿来微调了原木数据微调&#xff1a;纯手工标注 200 张左右原木图片&#xff0c;训练 20 轮的效果 PS&#xff1a;因为训练时间比较长 Google 的 Cola…

关于pyqt5与moviepy到打包的坑点

1,pyqt5 关于pyqt5 designer.exe 的使用主要就是了解pyqt5右侧菜单栏的功能使用 打包后的文件&#xff0c;需要继承改类&#xff0c;进行图形指令交互 关于pyqt5&#xff0c;要了解信号&#xff0c;和槽点的相互关系。 我在pyqt5中使用moviepy的时候&#xff0c;需要用到异步…

[VNCTF 2023] web刷题记录

文章目录 象棋王子电子木鱼BabyGo 象棋王子 考点&#xff1a;前端js代码审计 直接查看js源码&#xff0c;搜一下alert 丢到控制台即可 电子木鱼 考点&#xff1a;整数溢出 main.rs我们分段分析 首先这段代码是一个基于Rust的web应用程序中的路由处理函数。它使用了Rust的异步…

SpringMVC多种类型数据响应

SpringMVC多种类型数据响应入门 1.概念 RequestMapping 作用&#xff1a;用于建立请求URL和处理请求方法之间的对应关系 位置&#xff1a; 类上&#xff0c;请求URL的第一级访问目录。此处不写的话&#xff0c;就相当于应用的根目录 方法上&#xff0c;请求URL的第二级访问目…

交叉熵损失函数(Cross-Entropy Loss Function)

交叉熵损失函数&#xff08;Cross-Entropy Loss Function&#xff09; 在处理机器学习或深度学习问题时&#xff0c;损失/成本函数用于在训练期间优化模型。目标几乎总是最小化损失函数。损失越低&#xff0c;模型越好。交叉熵损失是最重要的成本函数。它用于优化分类模型。对…

10.0 输入输出 I/O

IO操作主要是指使用Java程序完成输入&#xff08;Input&#xff09;、输出&#xff08;Output&#xff09;操作。所谓输入是指将文件内容以数据流的形式读取到内存中&#xff0c;输出是指通过Java程序将内存中的数据写入到文件中&#xff0c;输入、输出操作在实际开发中应用较为…

TiDB专题---2、TiDB整体架构和应用场景

上个章节我们讲解了TiDB的发展和特性&#xff0c;这节我们讲下TiDB具体的架构和应用场景。首先我们回顾下TiDB的优势。 TiDB的优势 与传统的单机数据库相比&#xff0c;TiDB 具有以下优势&#xff1a; 纯分布式架构&#xff0c;拥有良好的扩展性&#xff0c;支持弹性的扩缩容…

一、Linux系统概述和安装

目录 1、Linux系统概述 2、Linux发行版介绍 3、虚拟机软件介绍 4、VMware安装 5、Linux系统&#xff08;CentOS&#xff09;系统安装 6、登录并查看IP地址 7、Linux连接工具CRT使用 7.1 概述 7.2 CRT安装 7.3 使用步骤 7.4 文件上传 8、Linux的快照 8.1 作用 8.2…