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

【JavaEE】——线程的安全问题和解决方式

阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!

目录

一:问题引入

二:问题深入

1:举例说明

2:图解双线程计算

​编辑

3:线程不安全原因的总结

(1)根本原因

(2)代码结构

(3)直接原因

(4)内存可见性问题

(5)指令重排序问题

5:解决问题的思路

(1)针对根本原因解决

(2)针对代码结构的原因解决

(3)针对直接原因——加锁

三:synchronized关键字(加锁)

1:synchronized

2:核心内容

(1)含义

(2)代码解释:

①“锁竞争”

②“加锁”

③“同一对象”

④“都要加锁”

3:变式

变式①:this

变式②: 类名.class

变式③:public前加synchronized

变式④:static方法前加synchronized(少见)


一:问题引入

用多线程,让计数器自增到1_0000

package thread;public class ThreadDemon19 {private static long count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 5000; i++) {count ++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 5000; i++) {count ++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("双线程的计算结果是:"+count);}}

package thread;public class ThreadDemon20 {private static long count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 1_0000; i++) {count ++;}});t1.start();t1.join();System.out.println("单线程的计算结果是:"+count);}}

 6ac3e10de1ea41f98d6f5891e5102852.png958d008681f84d10bceb10abe287dbd0.png

通过上述代码的举例,我们发现解决同一个问题,怎么最后的结果会不一样呢,真是奇了怪了。

二:问题深入

结果不一样,猜测是循环自增代码这一块出现问题

1:举例说明

我们知道cpu可以读取指令,解析指令,执行指令此处我们重点关注执行指令

count++,实际由三个指令构成的

(1)load:从内存中读取数据到cpu的寄存器当中

(2)add:把寄存器当中的值+1

(3)save:把寄存器当中的值写回到内存中

2:图解双线程计算

83b697bde121487f9842153d9f3a0844.png

c078365d354b4dce8eeaa2523e0ba043.png

t1,t2双线程的运行下,可能同一次读取操作中,t1和t2都读取到的是没有自增的数

可以通俗的理解,本来t1由数字1自增后到2,t2读取的应该是2,然后自增到3.

但是如果t2 在 t1把自增后的2 save回寄存器中  之前  读取的话 t2读到的就是1,最后只能自增到2

(可以理解成被覆盖了)

所以这就出现了矛盾(计算的数据越小矛盾越小,因为cpu运行速度很快,可能第一个线程运行结束了,第二个线程还没有开始运行)

(以上可以画出无数种情况,比如t1线程自增了2次,3次,t2线程才执行了1次。)(这就是线程的随机调度和抢占式执行)

3:线程不安全原因的总结

(1)根本原因

是线程的“抢占式执行和随机调度”

(2)代码结构

多个线程可以修改同一变量

(3)直接原因

是上述多线程修改变量这一操作并不是“原子性”的,而是可拆分的(就像我们上面画的图),这里就是操作系统底层结构的问题了

(4)内存可见性问题

(5)指令重排序问题

(4)(5)条上述代码没有涉及,我们后续再详细引入

5:解决问题的思路

为了确保结果的正确,我们得确保第一个线程save了过后,第二个线程再去load。这时第二个线程load到的数据才是自增过后正确的数据

(1)针对根本原因解决

不可行。如果要修改线程的“抢占式执行和随机调度”这一机制的话,就得修改操作系统中的内核,相当于是重新写了一个“新的系统”

(2)针对代码结构的原因解决

有些地方,代码结构可以进行修改,但是有些地方不可以,视情况而论

(3)针对直接原因——加锁

count++,由三个指令完成,我们如果给这三个指令打包成一个整体的话就可以避免线程出现问题了,也就是“加锁”

三:synchronized关键字(加锁)

1:synchronized

关键字:synchronized(对象){加锁内容};

注:synchronized的加锁依赖于对象

2:核心内容

(1)含义

如果第一个线程对某个对象上锁之后,第二个线程要想对同一个对象上锁,就必须等第一个线程释放锁,此时第二个线程是处于BLOCKED阻塞状态

package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-21* Time: 15:27*/
public class ThreadDemon21 {private static long count = 0;public static void main(String[] args) throws InterruptedException {Object locker = new Object();//创建一个object对象Thread t1 = new Thread(()->{for (int i = 0; i < 5000; i++) {synchronized (locker){  //锁到object这个对象上count++;}}});Thread t2 = new Thread(()->{for (int i = 0; i < 5000; i++) {synchronized(locker){   //锁到object这个对象上count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("计算结果是:" + count);}}

34ecb5cf59ec417d9278c9f3ee3dd8c0.png

(2)代码解释:

8265d6d44fe34545b81cad721dd02fb4.png

①“锁竞争”

通过“锁竞争”让让第二个线程无法插入到第一个线程的执行指令当中。

②“加锁”

“加锁”就是把count++中三个指令(load,add,save)打包成一个“原子性”的操作(最小单位的操作,再也不可分割了)

③“同一对象”

加锁的对象不同,“锁竞争”就不会发生,线程安全问题依旧存在

④“都要加锁”

如果第一个线程加锁,第二个线程不加锁,“锁竞争”也不会发生,线程安全问题依旧存在

3:变式

变式①:给this加锁

this指向的还是同一个对象,t1和t2依旧会产生“锁竞争”

package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-21* Time: 15:27*/
class Test{public long count = 0;public void add(){synchronized (this){count++;}}}
public class ThreadDemon22 {public static void main(String[] args) throws InterruptedException {Test test = new Test();Thread t1 = new Thread(()->{for (int i = 0; i < 5000; i++) {test.add();}});Thread t2 = new Thread(()->{for (int i = 0; i < 5000; i++) {test.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println("计算结果是:" + test.count);}
}
变式②: 类名.class

获取到当前类的对象,类对象包含了这个类的各种信息(类名字,属性,方法.......)

0d9678cdd4cc43c980ea87a69badfdf5.png

变式③:public前加synchronized

等价写法

149e4cdcd5764beea70abfd90e40972c.png26e85566700e4e468089ca8f887ae40e.png

变式④:给类对象加锁(static)
static方法前加synchronized(少见)

7907e54c80374913a85e7238d89f2940.png16e0df5e0bd34a6791810c9b39b7ebe5.png

如果synchronized加到static方法上,就相当于给类对象加锁了

相关文章:

【JavaEE】——线程的安全问题和解决方式

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c;你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;希望本文内容能够帮助到你&#xff01; 目录 一&#xff1a;问题引入 二&#xff1a;问题深入 1&#xff1a;举例说明 2&#xff1a;图解双线程计算…...

初步认识了解分布式系统

背景认识&#xff1a;我们要学习redis&#xff0c;还是得了解一下什么是分布式。为什么呢&#xff1f;因为redis只有在分布式系统中才能发挥它最大的作用&#xff0c;也就是领域展开&#xff0c;所以接下来我们就简单过一下什么是分布式系统 一些术语认识&#xff1a; &#x…...

react 为什么不能学习 vue3 进行静态节点标记优化性能?

因为 React 使用的是 JSX&#xff0c;而 JSX 本质上就是 JS 语言&#xff0c;是具有非常高的动态的&#xff0c;而 Vue 使用的 template 则是给了足够的约束&#xff0c;比如说 Vue 的 template 里面使用了很多特定的标记来做不同的事情&#xff0c;比如说 v-if 就是进行变量判…...

Elasticsearch黑窗口启动乱码问题解决方案

问题描述 elasticsearch启动后有乱码现象 解决方案&#xff1a; 提示&#xff1a;这里填写该问题的具体解决方案&#xff1a; 到 \config 文件下找到 jvm.options 文件 打开后 在文件末尾空白处 添加 -Dfile.encodingGBK 保存后重启即可。...

Logtus IT员工参加国际技术大会

Logtus IT的员工参加了国际技术大会&#xff0c;该大会致力于在金砖国家框架内开发俄罗斯的技术。该活动包括一个展览&#xff0c;俄罗斯开发商展示了他们的信息技术、电子和电信成就。展示了面向国内和国际市场&#xff08;包括政府机构&#xff09;的解决方案、产品和平台。 …...

ant design vue组件中table组件设置分组头部和固定总结栏

问题&#xff1a;遇到了个需求&#xff0c;不仅要设置分组的头部&#xff0c;还要在顶部有个统计总和的栏。 分组表头的配置主要是这个&#xff0c;就是套娃原理&#xff0c;不需要展示数据的直接写个title就行&#xff0c;需要展示数据的字段才需要详细的配置属性。 const co…...

2024年信息安全企业CRM选型与应用研究报告

数字化的生活给人们带来便利的同时也带来一定的信息安全隐患&#xff0c;如网络侵权、泄露用户隐私、黑客攻击等。在互联网高度发展的今天&#xff0c;信息安全与我们每个人、每个组织甚至每个国家都息息相关。 信息安全行业蓬勃发展。根据智研咨询数据&#xff0c;2021年&…...

【后端开发】JavaEE初阶——计算机是如何工作的???

前言&#xff1a; &#x1f31f;&#x1f31f;本期讲解计算机工作原理&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;【MySQL】MySQL中JDBC编程——MySQL驱动包安装——&#xff08;超详解&#xff09; &#x1f308;感兴趣的小伙伴看一看小编主…...

Linux(Ubuntu)源码安装postgresql16.3

文章目录 Linux&#xff08;Ubuntu&#xff09;源码安装postgresql016.3下载程序包编译安装软件初次执行configure错误调试1:configure: error: ICU library not found再次执行configureBuild 设置环境初始化数据库启动数据库参考 Linux&#xff08;Ubuntu&#xff09;源码安装…...

Python 入门教程(7)面向对象 | 7.6、多态

文章目录 一、多态1、鸭子类型2、实现多态的机制2.1、鸭子类型2.2、继承与重写 3、Python多态的优势4、总结 前言&#xff1a; 在面向对象编程&#xff08;OOP&#xff09;中&#xff0c;多态&#xff08;Polymorphism&#xff09;是一种非常重要的概念&#xff0c;多态就是同一…...

Cilium + ebpf 系列文章-什么是ebpf?(一)

前言&#xff1a; 这篇非常非常干&#xff0c;很有可能读不懂。 这里非常非常推荐&#xff0c;建议使用Cilium官网的lab来辅助学习&#xff01;&#xff01;&#xff01;Resources Library - IsovalentExplore Isovalents Resource Library, your one-stop destination for ins…...

RabbitMQ08_保证消息可靠性

保证消息可靠性 一、生产者可靠性1、生产者重连机制&#xff08;防止网络波动&#xff09;2、生产者确认机制Publisher Return 确认机制Publisher Confirm 确认机制 二、MQ 可靠性1、数据持久化交换机、队列持久化消息持久化 2、Lazy Queue 惰性队列 三、消费者可靠性1、消费者…...

恶意Bot流量识别分析实践

1、摘要 随着互联网的发展&#xff0c;自动化工具和脚本&#xff08;Bots&#xff09;的使用越来越普遍。虽然一些善意 Bots 对于网站的正常运行和数据采集至关重要&#xff0c;但恶意 Bots 可能会对网站带来负面影响&#xff0c;如爬取敏感信息、恶意注册、刷流量等。因此&am…...

Java2 实用教程(第6版)习题2 第四题

【源文件的命名与书中的不同】 四、阅读程序题 1、上机运行下列程序&#xff0c;注意观察输出的结果。 public class E2_1 {public static void main(String args[]){for(int i20302;i<20322;i){System.out.println((char) i);}} } 运行结果&#xff1a; 低 住 佐 佑 佒…...

HashMap和ConcurrentHashMap的区别

1.是什么 HashMap和ConcurrentHashMap都是Java集合框架中的成员&#xff0c;它们用于存储键值对&#xff0c;但它们在并发场景下的表现和行为有很大的不同。以下是它们之间的一些主要区别&#xff1a; 1. 并发安全性 HashMap: HashMap不是线程安全的。如果多个线程同时访问Has…...

css 下拉框展示:当hover的时候展示下拉框 z-index的用法解释

代码如下&#xff1a; <template><div class"outer"><div class"left"></div><div class"aTest2"><div class"box">显示方框</div><div class"aTest3"></div></…...

spring装配笔记

spring装配是个大课题&#xff0c;能懂一点是一点吧。 关于代码链路&#xff0c;最后的方式就是倒序摸索&#xff0c;正序那么多逻辑&#xff0c;没有一百万也差不多少&#xff0c;所以就用倒序。 .(点号)和#井号是一个意思&#xff0c;下面代码可能不详细区分&#xff0c;复…...

vscode【实用插件】Notes 便捷做笔记

安装 在 vscode 插件市场的搜索 Notes点 安装 安装成功后&#xff0c;vscode 左侧栏会出现 使用 初次使用 需先选择一个本地目录 重启 vscode 后&#xff0c;得到 切换笔记目录 新建笔记 快捷键为 Alt N 默认会创建 .md 文件 配合插件 Markdown Preview Enhanced 预览 .md…...

中间件:maxwell、canal

文章目录 1、底层原理&#xff1a;基于mysql的bin log日志实现的&#xff1a;把自己伪装成slave2、bin log 日志有三种模式&#xff1a;2.1、statement模式&#xff1a;2.2、row模式&#xff1a;2.3、mixed模式&#xff1a; 3、maxwell只支持 row 模式&#xff1a;4、maxwell介…...

postman控制变量和常用方法

1、添加环境&#xff1a; 2、环境添加变量&#xff1a; 3、配置不同的环境&#xff1a;local、dev、sit、uat、pro 4、 接口调用 5、清除cookie方法&#xff1a; 6、下载文件方法&#xff1a;...

Spring Boot 中整合 Kafka

在 Spring Boot 中整合 Kafka 非常简单&#xff0c;Spring Kafka 提供了丰富的支持&#xff0c;使得我们可以轻松地实现 Kafka 的生产者和消费者。下面是一个简单的 Spring Boot 整合 Kafka 的示例。 1. 添加依赖 首先&#xff0c;在 pom.xml 中添加 Spring Kafka 的依赖&#…...

什么是开放式耳机?具有什么特色?非常值得入手的蓝牙耳机推荐

开放式耳机是当下较为热门的一种耳机类型。它具有以下特点&#xff1a; 设计结构&#xff1a; 呈现开放式的构造&#xff0c;不会完全堵住耳道。如此一来&#xff0c;外界声音能够较容易地被使用者听到&#xff0c;在使用耳机时可以保持对周围环境的察觉。比如在户外&#xf…...

编译 FFmpeg 以支持 AV1 编解码器以及其他硬件加速选项(如 NVENC、VAAPI 等)

步骤 1: 安装必要的依赖 sudo apt update sudo apt install -y \autoconf automake build-essential cmake git libass-dev libfreetype6-dev \libsdl2-dev libtool libva-dev libvdpau-dev libxcb1-dev libxcb-shm0-dev \libxcb-xfixes0-dev pkg-config texinfo wget zlib1g-…...

解释一下Java中的多线程。如何创建一个新的线程?

在Java中&#xff0c;多线程是一种机制&#xff0c;允许一个程序同时执行多个任务或处理。每个任务被称为一个线程。 这种并行执行可以极大地提高应用程序的效率和响应速度。 例如&#xff0c;在开发一个桌面应用程序时&#xff0c;你可以使用一个线程来更新用户界面&#xf…...

Java语言程序设计基础篇_编程练习题**18.30 (找出单词)

题目&#xff1a;**18.30 (找出单词) 编写一个程序&#xff0c;递归地找出某个目录下的所有文件中某个单词出现的次数。从命令行如下传递参数&#xff1a; java Exercise18_30 dirName word 习题思路 &#xff08;读取路径方法&#xff09;和18.28题差不多&#xff0c;把找…...

MyBatis中 #{} 和 ${} 的区别

1. #{id}&#xff08;参数占位符&#xff09; 作用: 使用 #{id} 时&#xff0c;MyBatis 会将 id 参数绑定为 JDBC 的参数。这种方式能够有效防止 SQL 注入攻击&#xff0c;因为它会进行参数的预处理&#xff0c;将参数值作为数据类型的绑定&#xff0c;而不是直接插入到 SQL 语…...

Android Perfetto 学习

1、如何抓取性能日志 方式1、通过手机里的System Tracing抓取 1、点击Settings->System->Developer options->System Tracing->Record trace 打开 2、操作完成后&#xff0c;点击Settings->System->Developer options->System Tracing->Record trace…...

ES数据的删除与备份

背景 需要删除索引下满足指定条件的文档数据&#xff0c;并将删除的数据进行备份。 操作步骤 新建索引 该索引结构与映射关系与原索引一致 查看原索引设置 GET /tb/_settings结果&#xff1a; {"tb" : {"settings" : {"index" : {"ro…...

论文解读《Object-Centric Learning with Slot Attention》

系列文章目录 文章目录 系列文章目录论文细节理解 1. 研究背景2. 论文贡献3. 方法框架3.1 Slot Attention模块3.2 无监督对象发现架构 4. 研究思路5. 实验6. 限制 论文细节理解 supervised property prediction tasks是什么&#xff1f; Supervised property prediction tasks…...

YOLOv8+注意力机制+PyQt5玉米病害检测系统完整资源集合

资源包含可视化的玉米病害检测系统&#xff0c;基于最新的YOLOv8注意力机制训练的玉米病害检测模型&#xff0c;和基于PyQt5制作的可视玉米病害系统&#xff0c;包含登陆页面和检测页面&#xff0c;该系统可自动检测和识别图片或视频当中出现的七类玉米病害&#xff1a;矮花叶病…...