【多线程】线程安全

🥰🥰🥰来都来了,不妨点个关注叭!
👉博客主页:欢迎各位大佬!👈

在这里插入图片描述

文章目录

  • 1. 观察线程不安全
  • 2. 线程不安全的原因
    • 2.1 抢占式执行
    • 2.2 修改共享数据
    • 2.3 修改操作不是原子的
    • 2.4 内存可见性
      • 特别介绍 "工作内存"和"主内存"
    • 2.5 代码顺序性

1. 观察线程不安全

多线程带来的风险——线程安全(即一个代码在多线程环境下,执行会出现问题)
那么为什么会出现线程安全问题呢???
本质原因线程在系统中的调度是无序的/随机的,即抢占式执行
我们创建2个线程,分别自增3w次,最后打印结果,想要得到6w的结果,通过下述代码,观察线程不安全,到底是怎么个事呢~我们一起来看看吧!

// 线程安全问题
public class Counter {private int count = 0;public void add() {count++;}public int getCount() {return count;}
}
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();//创建两个线程 两个线程分别对这个counter 自增3w次Thread t1 = new Thread(()->{for(int i = 0; i < 300000; i++) {counter.add();}});Thread t2 = new Thread(()->{for(int i = 0; i < 300000; i++) {counter.add();}});t1.start();t2.start();//等待两个线程执行完成t1.join();t2.join();System.out.println(counter.getCount());}
}

多次运行结果如下:
在这里插入图片描述
通过上面程序,两个线程针对同一个变量各自自增3w次,运行结果可以看到,预期结果为6w,实际结果却像是一个随机值,每次运行程序后的结果不一样
实际结果和预期结果不相符,这就是bug!由多线程引起的bug,即线程不安全/线程安全问题
为什么会出现这个情况呢?下面进行具体的分析:
与线程的随机调度密切相关~
具体分析
count++的操作 本质上是由三个CPU指令构成,如下:
1)load 把内存中的数据读取到CPU寄存器中
2)add 把寄存器中的值,进行+1运算
3)save 把寄存器中的值写回到内存中
由于多线程调度顺序是不确定的,实际执行的过程中,t1 和 t2 这两个线程的 ++ 操作实际的指令排列顺序就会有很多个可能,通过下面指令排序示意图,我们来具体感受一下:
在这里插入图片描述
补充说明
t1 和 t2 是两个线程,肯定是运行在不同的CPU的核心上,也可能是运行在同一个CPU核心上(分时复用,并发)
这两种情况的最终效果一致,下面拿简单的在不同CPU核心上运行的情况为例进行演示,分析具体:
在这里插入图片描述
同理分析图3,可以得到:
其中图2、3指令的排列情况,此时看到两个线程各自自增1次,最终结果为2,循环3w次,可保证结果为6w,此时是没有bug的,结果正确!!!但是线程调度是无序的,上述代码我们并不知道是按照哪种情况来的,两个线程自增的过程中,到底经历了什么,我们并不清楚“顺序执行”与“交错执行”的次数,再来看看其它情况:
在这里插入图片描述

1.t2线程load,将内存的数据0读取到CPU寄存器中
2.t1线程load,将内存的数据0读取到CPU寄存器中
3.t1线程add,将寄存器中的值进行+1,为1
4.t1线程save,将寄存器中的值1写回到内存中,内存中为1
5.t2线程add,将寄存器中的值进行+1,为1
6.t2线程save,将寄存器中的值1写回到内存中,覆盖掉之前的内存值1,但内存值目前仍然为1,出现bug

此时发现,按照上述执行过程,两个线程自增两次,最后结果为1,说明bug出现了,其中一次自增的结果,被另一个给覆盖了所以出现这种情况,出现bug后,得到的结果一定是 <= 6w
思考但结果一定是 >= 3w吗???
答案:不是,实际上,结果可以是小于3w的,只是概率更低了
比如这种情况,如图所示:
在这里插入图片描述
t1自增1次,t2自增2次,最后结果还是1,两次无效自增,当然t2也可能让t1出现更多次的无效自增,此时完全可以实现结果小于3w,只不过是概率问题
总结
线程安全问题归根结底是因为线程的无序调度,导致了执行顺序的不确定,结果就发生了变化
下面具体细分线程不安全的原因,一起来看看~

2. 线程不安全的原因

2.1 抢占式执行

抢占式执行是根本原因!!!线程在系统中的调度是随机的!!!

2.2 修改共享数据

多个线程修改同一个变量,是线程不安全的!!!以下几种情况线程安全:

一个线程修改同一个变量——> 安全
多个线程读取同一个变量——> 安全
多个线程修改不同的变量——> 安全

在上述线程不安全的代码中,涉及到多个线程对同一个变量 counter.count 变量进行修改,即 counter.count 变量是一个线程都能够访问的共享数据
这个原因很好理解,如果多个线程修改同一个变量,由于抢占式执行,这个线程修改完,又被另一个线程修改完的数据给覆盖,就会导致线程不安全问题

2.3 修改操作不是原子的

原子性】不可分割的最小单位
注意:一条 java 语句不一定是原子的,也不一定只是一条指令
比如上述代码中 counter.count变量的 ++ 的操作里,其实是有三个操作组成,即对应三条指令:
1)从内存中把数据读到CPU
2)把寄存器中的值,进行+1运算
3)把寄存器中的值写回到内存中
其中的某个操作,对应单个的CPU指令,即原子的,如果这个操作对应多个CPU指令,大概率就不具有原子性啦
补充】如果直接使用 = 赋值,就是一个原子的操作
不保证原子性会给多线程带来什么问题呢?
如果一个线程正在对一个变量操作,在操作过程中其他线程插入进来了,如果这个操作被打断了,结果可能就是错误的!!!即正是因为不是原子的,导致这两个线程的指令排列存在很多种可能(与线程的抢占式调度密切相关,如果线程不是"抢占"的,就算这个操作不是原子的,那也是没有什么问题的)

上述三个场景均是当前代码 counter.count++ 线程不安全原因,另外还有几种场景会引起线程不安全问题,和当前的栗子count++ 无关,场景如下:

2.4 内存可见性

含义】可见性指的是一个线程对共享变量值的修改,能够及时被其它线程看到
通俗的说,所谓内存可见性,就是在多线程环境下,编译器对于代码的优化,产生了误判,从而引起bug,进一步导致代码的bug
Java内存模型(JMM)】Java虚拟机规范了Java内存模型
目的】屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果

下面的这一套的说法,称为JMM,其概念来自于 jvm 规范文档,相当于是官方给出的这套说法在描述内存可见性,会涉及到主内存、工作内存这两个词,下面具体来介绍介绍这两个概念~

特别介绍 “工作内存"和"主内存”

内存可见性问题,举个栗子,如下:
t1 线程频繁读取主内存,效率是十分低的,就被优化成直接读自己的工作内存
t2 线程修改主内存的结果,由于 t1 没有读主内存,仍然读的是自己工作内存,修改不能被识别到,导致出现bug
(通俗一点讲就是,t1 需要经常读取内存,一直是一个值,很聪明,直接优化读自己的工作内存,但是 t2 修改了主内存,t1 仍然在读工作内存,则会出错)
在这里插入图片描述
这里和我们平时所了解的不太一样,所以在这里特别说明
工作内存CPU寄存器和缓存
主内存才是我们平时所说的内存条,真正的内存
为什么会不一致呢
其实是因为,这两个词语翻译自英文
工作内存 —— work memory
主内存—— main memory
事实上,memory 这个英文单词并不一定就特指内存条,它其实也可以翻译成"存储区",单纯地表示"一个用来存储的空间"而已,在翻译过来的时候,仍将 memory 翻译成"内存",work memory 就被翻译为"工作内存"

这样就会让我们误会:内存条还分为不同的种类吗?根据功能分为"工作内存"和"主内存"吗?
其实并不是的!!!实际上所说的"工作内存",应理解为"工作存储区"更为合适,它是CPU寄存器和缓存,与内存条无关的!!!

为什么 Java 官方用work memory 描述呢
主要是因为,Java是跨平台的意味着它必须满足如下条件:
1)兼容多种操作系统
2)兼容多种硬件设备,尤其是 CPU!!! 而不同的硬件之间,CPU会有比较大的差异
比如,以前的CPU上只有寄存器,但是现在的CPU上除了寄存器,还有缓存,随着时代的发展,有的CPU还有好几个缓存,目前常见的是三级缓存:L1、L2、L3,如下:
在这里插入图片描述
这样一来,Java官方文档要指CPU内部的存储空间,如果只提到"CPU寄存器",那就十分不严谨,因为还有各种缓存,如果使用"CPU寄存器和缓存"描述,名字又过长过于麻烦,因此,官方直接发明了一个新的术语:work memory 来代表CPU寄存器和缓存,即CPU内部的存储空间
为什么要这么麻烦拷来拷去
因为 CPU 访问自身寄存器的速度以及高速缓存的速度,远远超过访问内存的速度(快3 - 4个数量级, 即快几千倍, 上万倍)
比如在某个代码中要连续 1000 次读取某个变量的值,如果 1000 次都从内存读,速度很慢,但是如果
只是第一次从内存读,读到的结果缓存到 CPU 的某个寄存器中,那么后 999 次读数据就不必直接访问
内存了,效率就会大大提高!!!
补充说明
缓存的读取速度介于寄存器和内存之间,其中三级缓存中,L1 最快,仍比寄存器慢,但空间最小,L3 最慢,但也比内存快很多,空间最大
在这里特别说明一下
快和慢都是相对的,CPU 访问寄存器速度远远快于内存, 但是内存的访问速度又远远快于硬盘,对应的,CPU 的价格最贵,内存次之,硬盘最便宜
访问速度:CPU >> 内存 >> 硬盘
价格:CPU >> 内存 >> 硬盘

2.5 代码顺序性

含义】代码的顺序性,即指令重排序~具体是什么呢?下面通过一个例子来进行解释:
1)去菜鸟驿站取杯子快递
2)回宿舍玩耍
3)去菜鸟驿站取本子快递
如果是在单线程的情况下,JVM、CPU指令集合会对其进行优化,比如按照 1->3->2的方式执行,是没有任何问题的,这样执行的方式还少跑一趟,直接一起取了,这种即为指令重排序

编译器对于指令重排序的前提是"保持逻辑不发生变化",这点在单线程的环境下比较容易判断,但是在多线程环境中,较为复杂,就不那么容易判断了,多线程的代码执行的复杂程度更高,编译器很难再编译阶段对代码的执行效果进行预测,因此重排序很容易导致优化前后的不等价,这就会出现问题

特别说明】重排序是一个很复杂的话题,涉及到CPU以及编译器的一些底层工作管理,此处不作太多补充
💛💛💛本期内容回顾💛💛💛
在这里插入图片描述✨✨✨本期内容到此结束啦~下期内容将介绍如何解决线程安全问题(期待ing)

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

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

相关文章

【高级篇】分区与分片:MySQL的高级数据管理技术(十三)

引言 在上一章,我们探讨了MySQL的主从复制与高可用性,这是构建健壮数据库架构的基石。现在,让我们深入到更高级的主题——分区与分片,这些技术对于处理大规模数据集和提升数据库性能至关重要。我们将详细介绍表分区的概念、类型及分片技术的应用,为下一章讨论MySQL集群与…

经纬恒润推出面向教育行业的仿真测试实验室

随着汽车行业向电动化和智能化的转型&#xff0c;车辆的智能水平和复杂度不断提升&#xff0c;整车的开发周期却越来越短&#xff0c;测试要求越来越高&#xff0c;自动化测试成为必由之路。作为行业技术创新和引领者&#xff0c;高校面临着新能源和智能驾驶等新技术的众多挑战…

HTML(15)——盒子模型

盒子模型组成 内容区域 -width&height内边距-padding &#xff08;出现在内容与盒子边缘之间&#xff09;边框线-border外边距-margin &#xff08;出现在盒子外面&#xff09; div { width: 200px; height: 200px; background-color: rgb(85, 226, 193); padding: 20px; …

如何降低MCU系统功耗?

大家在做MCU系统开发的时候&#xff0c;是否也碰到过降低MCU系统功耗的需求&#xff1f; MCU系统整板功耗是个综合的数据&#xff0c;包括MCU功耗以及外部器件功耗&#xff0c;在此我们主要介绍如何降低MCU的功耗&#xff1a; 可以在满足应用的前提下&#xff0c;降低MCU的运…

百元蓝牙耳机哪款性价比高?盘点性价比高的百元蓝牙耳机品牌

在如今快节奏的生活中&#xff0c;蓝牙耳机已经成为人们日常生活中不可或缺的配件。然而&#xff0c;市面上百元左右性价比高的蓝牙耳机琳琅满目&#xff0c;消费者往往难以选择到一款质量好、耐用的产品。我们希望可以为广大消费者提供一些参考和建议&#xff0c;接下来&#…

KVM网络模式设置

一、KVM网络模式介绍 1、NAT ( 默认上网 ) 虚拟机利用host机器的ip进行上网,对外显示一个ip;virbr0是KVM 默认创建的一个 Bridge,其作用是为连接其上的虚机网卡提供NAT访问外网的功能,默认ip为192.168.122.1 2、自带的Bridge 将虚拟机桥接到host机器的网卡上,vm和ho…

vue 实现 word/excel/ppt/pdf 等文件格式预览操作

效果图&#xff1a; 问题描述&#xff1a;一般情况下使用iframe标签就可以实现文件预览&#xff0c;但是这个标签只针对于ppt和pdf是有效的。对于doc文件就需要借助第三方插件&#xff08;vue-office/docx&#xff09;来实现预览了。下面介绍使用方法。 安装插件&#xff1a;n…

云通SIPX,您的码号资源智能调度专家!

在数字化转型的浪潮中&#xff0c;号码资源作为企业与客户沟通的重要桥梁&#xff0c;其管理效率直接关系到企业运营的成败。随着运营商对号码资源管理的规范化和精细化&#xff0c;企业对高效、智能的号码资源管理需求日益增长&#xff0c;以实现对外呼叫的降本增效。 一、什么…

教程:LVM操作讲解

LVM简介 在系统运维过程中&#xff0c;对磁盘扩缩容是常见的操作。如何高效的管理磁盘容量&#xff0c;lvm提供了很好的解决方案。 LVM将磁盘抽象成PV、VG、LV&#xff0c;方便用户进行磁盘管理&#xff0c;简单来讲&#xff0c;是由物理磁盘划分成PV&#xff0c;PV加入到具体…

【AI大模型RAG】深入探索检索增强生成(RAG)技术

目录 1. 引言2. RAG技术概述2.1 RAG技术的定义2.2 RAG技术的工作原理2.3 RAG技术的优势2.4 RAG技术的应用场景 3. RAG的工作流程3.1 输入处理3.2 索引建立3.3 信息检索3.4 文档生成3.5 融合与优化 4. RAG范式的演变4.1 初级 RAG 模型4.2 高级 RAG 模型4.3 模块化 RAG 模型优化技…

HBase:大数据时代的分布式存储利器

HBase&#xff1a;大数据时代的分布式存储利器 HBase&#xff1a;大数据时代的分布式存储利器1. HBase简介2. HBase特点3. HBase应用场景4. 总结 HBase&#xff1a;大数据时代的分布式存储利器 随着互联网和大数据技术的飞速发展&#xff0c;数据存储和计算需求呈现出爆炸式增…

el-select多选超过两个选项省略

前言 相信大家都遇到过这种情况&#xff1a;Element下拉框多选的时候有个毛病&#xff0c;就是选的数量过多就会把下拉框撑高&#xff0c;从而影响布局&#xff1b;但是如果使用了里面collapse-tags属性&#xff0c;element设置的只显示一个&#xff0c;超过一个就隐藏省略了&…

wps的domain转为shp矢量

wps的namelist制作、python出图和转矢量 简介 wps&#xff08;WRF Preprocessing System&#xff09;是中尺度数值天气预报系统WRF(Weather Research and Forecasting)的预处理系统。 wps的安装地址在GitHub上&#xff1a;https://github.com/wrf-model/WPS 下载完成后&…

一步步带你解锁Stable Diffusion:老外都眼馋的 SD 中文提示词插件分享

大家好我是极客菌&#xff01;今天我们继续来分享一个外国人都眼馋的 SD 中文提示词插件。 那我们废话不多说&#xff0c;直接开整。 SD 的插件安装&#xff0c;小伙伴们应该都会了吧&#xff0c;我这里再简单讲下哦&#xff0c;到「扩展」中的「可下载」中点击「加载扩展列表…

分布式锁实现方案-基于Redis实现的分布式锁

目录 一、基于Lua看门狗实现 1.1 缓存实体 1.2 延迟队列存储实体 1.3 分布式锁RedisDistributedLockWithDog 1.4 看门狗线程续期 1.5 测试类 1.6 测试结果 1.7 总结 二、RedLock分布式锁 2.1 Redlock分布式锁简介 2.2 RedLock测试例子 2.3 RedLock 加锁核心源码分析…

560. 和为 K 的子数组

题目描述 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 解题 简单直接, 但时间复杂度最高 O(n3) class Solution {func subarraySum(_ nums: [Int], _ k: Int) -> Int {var t…

华三中小企业组网

一、组网需求 在中小园区中&#xff0c;S5130系列或S5130S系列以太网交换机通常部署在网络的接入层&#xff0c;S5560X系列或 S6520X系列以太网交换机通常部署在网络的核心&#xff0c;出口路由器一般选用MSR系列路由器。 核心交换机配置VRRP保证网络可靠性。园区网中不同的…

哪些AI生图软件值得推荐,有需要的建议收藏!

人工智能(AI)已经渗透到我们生活的方方面面&#xff0c;AI生图软件就是其中的一种&#xff0c;它们能够帮助我们快速生成高质量的图片&#xff0c;无论是社交媒体的配图&#xff0c;还是设计作品的素材&#xff0c;都能够得到极大的帮助。那么哪些AI生图软件值得推荐呢? 首先&…

自定义APT插件导致IDEA调试时StreamTrace(跟踪当前流链)报internal error(内部错误)

IDEA里面debug的时候&#xff0c;针对stream流提供了流追踪调试功能&#xff0c;方便大家调试stream流代码。 最近改其他人代码&#xff0c;需要用到这个&#xff0c;发现提示内部错误。 然后百度一圈发现没啥解决方案&#xff0c;就自己看IDEA的日志&#xff0c;看看是什么引…

Centos安装redis(附:图形化管理工具)

第一步&#xff1a;下载redis wget http://download.redis.io/releases/redis-6.2.7.tar.gz 第二步&#xff1a;解压 tar zxvf redis-6.2.7.tar.gz 第三步&#xff1a;安装依赖环境 yum -y install gcc-c第四步&#xff1a;安装依赖环境 make install第五步&#xff1a;修…