多线程带来的的风险-线程安全

v2-7bdded212ba4d459fb4fe10bcaa0021d_b

多线程带来的的风险-线程安全

~~ 多线程编程中,最难的地方,也是一个最重要的地方,还是一个最容易出错的地方,更是一个面试中特别爱考的地方.❤️❤️❤️

线程安全的概念

万恶之源,罪魁祸首是多线程的抢占式执行,带来的随机性.~~😕😕😕
如果没有多线程,此时程序代码执行顺序就是固定的,代码顺序固定,程序的结果就是固定的.
如果有了多线程,此时在抢占式执行下,代码执行的顺序,会出现更多的变数!!!
代码执行顺序的可能性就从一种情况变成无数种情况!!!
所以就需要保证这无数种线程调度顺序的情况下,执行的结果都是正确的!!!只要是有一种情况下,代码结果不正确,就视为线程不安全!!!

问题来了:能否消除这样的随机性了🤔🤔🤔?
调度的源头来自于操作系统的内核实现.
1.作为程序猿的我们改不了.😂😂😂
2.即使改了自己的操作系统,也无法推广开来,因为全世界大多数操作系统都是这样的,已成定局!😕😕😕

观察线程不安全(代码)😍😍😍

class Counter{public int count = 0;public void add(){count++;}
}
public class ThreadDemo13 {public static void main(String[] args) {Counter counter = new Counter();// 创建两个线程, 两个线程 counter 来调用 5W 次的add方法Thread t1 = new Thread(()->{for (int i = 0; i < 5_0000; i++) {counter.add();}});Thread t2 = new Thread(()->{for (int i = 0; i < 5_0000; i++) {counter.add();}});// 启动线程t1.start();t2.start();// 等待两个线程结束try {t1.join();t2.join();} catch (InterruptedException e) {throw new RuntimeException(e);}// 打印最终的 count 值 预期结果: count = 10WSystem.out.println("count = "+ counter.count);}
}

运行结果:

image-20230923031309538
我们的需求是两个线程各自自增 5w次,一共自增 10w次,
预期结果是 10w,实际结果不是 10w而且每次都不一样.
程序出现了bug(程序不符合需求,就是bug).
注:这个就是典型的线程安全问题!!!😥😥😥

线程不安全的原因

~~ 为什么程序就出现了这个情况🤔🤔🤔?

线程与指令之间的关系:

一个线程要执行,就需要先编译成很多的CPU指令,写的任何一个代码都是要编译成很多的CPU指令的!!!
个人理解:一个线程是来完成一个任务,要做一些工作,而这个工作是可以分解成一个一个的小步骤的,每个小步骤就是一个指令.
由于线程的抢占式执行,导致当前执行到任意一个指令的时候,线程都有可能被调度走,然后CPU让别的线程来执行.

寄存器,CPU里重要的组成部分,寄存器也能存数据,空间更小,访问速度更快,CPU进行的运算都是针对寄存器(准确的说,是通用寄存器,如EAX,EBX,ECX)中的数据进行的

count++;
++ 操作本质上要分成三步
1.先把内存中的值,读取到CPU的寄存器中 ~~load
2.把CPU寄存器里的数值进行 +1运算 ~~add
3.把得到的结果写回到内存中 ~~ save
注:load,add,save就是CPU上执行的三个指令(被视为机器语言).

两个线程并发的执行count++,此时就相当于两组load,add,save进行执行,此时不同的线程调度顺序就可能会产生一些结果上的差异.

**作图来理解多线程的调度:**❤️❤️❤️

image-20230923125739880

分析执行过程:

image-20230923133152779

思考一下🤔🤔🤔:

出现bug 之后,得到的结果一定是 <= 10w, 结果是一定 >= 5w 嘛?
极端情况下,所有的执行都是交错执行,是否就是 5w 呢??
实际上,结果是可以小于 5w ,只是概率更低了!!
image-20230923133717932

根结底线程安全问题全是因为==线程的无序调度(罪魁祸首,万恶之源)==导致了执行顺序不确定结果就变化了!!!

总结(线程不安全的原因)😊😊😊

  1. [根本原因] 抢占式执行,随机调度 ~~ 对此,我们无能为力.

  2. 代码结构:

    • 多个线程 同时 修改 同一个变量 ~~ 不安全!!! 😥😥😥

    • 一个线程,修改一个变量,安全.

    • 多个线程读取同一个变量,安全.

    • 多个线程修改多个不同的变量,安全.

    • 注1: 因此可以通过代码结构来规避这个线程不安全问题,
      但是因为需求问题,代码结构无法进行调整(这种方法使用频率并不高).

    • 注2: 修改 => 不可变对象是无法修改的,天然就是线程安全的!!!

  3. 原子性. 如果修改操作是原子的,出现问题概率小;如果是非原子的,出现问题概率极高(线程不安全问题,其实本质上是事务的脏读问题,之前博主写的博客解释过相关概念,在此不做解释啦!ヾ(❀^ω^)ノ゙

    • 原子: 不可拆分的基本单位.

      上述 count ++ 操作就不是原子,里面可以拆分成三个操作,load,add,save.某个操作,对应单个CPU指令,就是原子的,如果这个操作对应多个CPU指令,大概率就不是原子的.比如直接使用 = 赋值,就是一个原子的操作

  4. 内存可见性,引起的线程不安全

    • 一个线程读,一个线程改,可能出现读的结果和预期不符合的问题.
  5. 指令重排序,引起的线程不安全

    • 本质上是编译器优化,优化出bug了.优化:编译器觉得程序猿写的代码太low了,对代码进行调整,在保持代码逻辑不变的情况下,调整代码的执行顺序,从而加快程序的执行效率.

线程不安全问题的解决

**如何解决线程不安全问题?**🤨🤨🤨🤨
最主要的手段就是从这个原子性下手,通过加锁不能把非原子的操作变成原子的.
即解决前面代码的线程不安全问题,就是通过加锁让count++变成原子的.
synchronized public void add(){ count++; }

加了 synchronized 之后,进入方法就会加锁,出了方法就会解锁.
如果两个线程同时尝试加锁,此时一个能获取锁成功,另一个只能阻塞等待(处于BLOCKED状态),一直阻塞到刚才的状态解锁(释放锁),当前线程才能加锁成功!!! => 操作系统的基本设定,系统里的锁“不可剥夺”特性,一旦一个线程获取到锁,除非它主动释放,否则无法强占.

加锁,说是保证原子性,不是让load,add,save三个操作一次完成,也不是就是让其它也想操作的线程阻塞等待了
image-20230923205141702

image-20230923203926850

虽然加锁之后,算得慢了,但是还是比单线程要快,加锁只是针对count++加锁了,除了count++之外,还有for循环的代码,for循环代码是可以并发执行的(线程t1和线程t2各自修改各自for循环的局部变量i,是没问题的),只是count++串行执行了.
一个任务中,一部分可以并发,一部分串行,仍然是比所有代码串行要快的.


加锁,是要明确执行对哪个对象加锁的.如果两个线程针对同一个对象加锁,会产生阻塞等待(锁竞争/锁冲突),如果两个线程针对不同对象加锁,不会阻塞等待(不会锁冲突/锁竞争).

synchronized的使用方法😊😊😊😊

  1. 修饰方法
    • 修饰普通方法
      • 进入方法就加锁,离开方法就解锁
    • 修饰静态方法
      • 通理也是这样,进入方法就加锁,离开方法就解锁
    • 但是这两种修饰方法,加锁的“对象”不同,修饰普通方法,锁对象就是this,修饰静态方法,锁对象就是类对象.
  2. 修饰代码块
    • 显示/手动指定锁对象

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

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

相关文章

API(十)时间相关的SDK

一 时间相关的SDK ① 时间记录的必要性 1、案发现场的时间点2、通过时间判断性能3、时间的不准确性,日志落盘时间 --> 缓冲区导致延迟 ② 使用哪些日期和时间的函数 1、lua 标准时间函数,函数 os.time、os.date 和 os.difftime 提供了所有日期和时间2、在 openresty…

windows 深度学习环境部署

1. 根据显卡配置安装适合的CUDA,查看显卡配置可在显卡控制面板上查看,安装是否成功可通过nvidia-smi查看&#xff1b;注意安装路径 https://developer.nvidia.com/cuda-toolkit-archive 2. 根据cuda安装合适的cudnn&#xff0c;需要注册NVIDIA cuDNN Archive | NVIDIA Devel…

自定义数据类型

前言&#xff1a;小伙伴们又见面啦&#xff0c;今天这篇文章&#xff0c;我们来谈谈几种自定义数据类型。 目录 一.都有哪些自定义数据类型 二.结构体 结构体内存对齐 1.如何对齐 2.为什么要对齐 3.节省空间和提升效率的方法 &#xff08;1&#xff09;让占用空间小的成员…

Qt5开发及实例V2.0-第二十三章-Qt-多功能文档查看器实例

Qt5开发及实例V2.0-第二十三章-Qt-多功能文档查看器实例 第23章 多功能文档查看器实例23.1. 简介23.2. 界面与程序框架设计23.2.1. 图片资源23.2.2. 网页资源23.2.3. 测试用文件 23.3 主程序代码框架23.4 浏览网页功能实现23.4.1 实现HtmIHandler处理器 23.5. 部分代码实现23.5…

AI 编码助手 Codewhisperer 安装步骤和使用初体验

文章作者&#xff1a;为了自己加油 最近亚⻢逊云科技推出了一款基于机器学习的AI编程助手 Amazon Code Whisperer&#xff0c;可以实时提供代码建议。在编写代码时&#xff0c;它会自动根据现有的代码和注释给出建议。Amazon Code Whisperer与 GitHub Copilot 类似&#xff0c;…

Vite的安装与使用

Vite也是前端的构建工具&#xff0c;相较于Webpack&#xff0c;Vite使用了不同的运行方式&#xff1a; 开发时并不对项目进行打包&#xff0c;而是直接采用ESM的方式来运行项目。在项目部署时再进行打包。 因此vite的执行速度相较于Webpack快了许多&#xff0c;操作起来也比W…

SQL中:提示不允许修改表结构,如何更改

SQL&#xff1a;不允许修改表结构 步骤图例注意 步骤 选择菜单栏中的“工具”-“选项”&#xff0c;在选项对话框左栏中找到“设计器”&#xff0c;在设计器右边取消勾选“阻止保存要求重新创建表的更改”即可。 图例 注意 设计表时&#xff0c;尽量一次性设计成功&#xff…

Android12之解封装NuMediaExtractor::setDataSource过程(四十七)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…

Springboot ruoyi配置mysql备份定时任务

一、RuoYiConfig.class 新增获取备份路径方法 public static String getDataBaseBackUp() {return getProfile() "/dbBackUp";} 二、RyTask&#xff1a;新增备份数据库方法 mySqlDump方法&#xff1a;参数详见代码 package com.ruoyi.quartz.task;import cn.hut…

【动手学深度学习-Pytorch版】序列到序列的学习(包含NLP常用的Mask技巧)

序言 这一节是对于“编码器-解码器”模型的实际应用&#xff0c;编码器和解码器架构可以使用长度可变的序列作为输入&#xff0c;并将其转换为固定形状的隐状态&#xff08;编码器实现&#xff09;。本小节将使用“fra-eng”数据集&#xff08;这也是《动手学习深度学习-Pytor…

linux用户和权限命令学习记录

文章目录 版权声明root用户&#xff08;超级管理员&#xff09;su和exit命令sudo命令为普通用户配置sudo认证 用户、用户组管理用户组管理getent命令 查看权限控制认知权限信息 修改权限控制chmod修改文件、文件夹的权限权限的数字序号chown修改所属用户、用户组 版权声明 本博…

华为NFC设置教程(门禁卡/公交卡/校园卡等)

今天把华为NFC设置教程分享给大家 出门带门禁卡、校园卡、银行卡、身份证……东西又多&#xff0c;携带又麻烦&#xff0c;还容易搞丢&#xff0c;有没有一种方法可以把它们都装下&#xff1f;有&#xff01;只要一部手机&#xff0c;出门不带卡包&#xff0c;各种证件&#x…

机器学习第十课--提升树

一.Bagging与Boosting的区别 在上一章里我们学习了一个集成模型叫作随机森林&#xff0c;而且也了解到随机森林属于Bagging的成员。本节我们重点来学习一下另外一种集成模型叫作Boosting。首先回顾一下什么叫Bagging? 比如在随机森林里&#xff0c;针对于样本数据&#xff0c;…

使用命令行快速创建Vite项目

一、构建项目 在终端中使用如下命令行&#xff1a; npm create vite 二、定义项目名称 三、选择项目类型 Vanilla是我们常用的JavaScript&#xff0c;Vue和React是常用前端框架&#xff0c;可以根据自己的需要进行选择 通过上下键进行选择&#xff0c;按下回车进行确认 创建…

docker容器安装MongoDB数据库

一&#xff1a;MongoDB数据库 1.1 简介 MongoDB是一个开源、高性能、无模式的文档型数据库&#xff0c;当初的设计就是用于简化开发和方便扩展&#xff0c;是NoSQL数据库产品中的一种。是最 像关系型数据库&#xff08;MySQL&#xff09;的非关系型数据库。 它支持的数据结构…

Neo4j图数据库_web页面关闭登录实现免登陆访问_常用的cypher语句_删除_查询_创建关系图谱---Neo4j图数据库工作笔记0013

由于除了安装,那么真实使用的时候,就是导入数据了,有了关系和节点的csv文件以后如果用 cypher进行导入数据和创建关系图谱,还有进行查询,以及如果导入错误如何清空,大概是这些 用的最多的,单独把这些拿进来,总结一下,用的会比较方便. 1.实现免登陆访问: /data/module/neo4j-…

Spring学习笔记6 Bean的实例化方式

Spring学习笔记5 GoF之工厂模式_biubiubiu0706的博客-CSDN博客 Spring为Bean提供了多种实例化方式,通常包括4中(目的:更加灵活) 1.通过构造方法实例化 2.通过简单工厂模式实例化 3.通过factory-bean实例化 4.通过FactoryBean接口实例化 新建模块 spring-005 依赖 <!--S…

结构体,联合体与位段

1.结构体的内存对齐(计算结构体的大小) 1.1 为什么需要结构体内存对齐? 原因1:平台原因 不是所有的硬件平台都能访问任意地址上的任意数据的&#xff1b;某些平台只能在某些地址处取得某些特定类型的数据&#xff0c;否则抛出硬件异常。 比如&#xff0c;当一个平台要取一个…

Qt5开发及实例V2.0-第二十二章-Qt.Quick Controls 2新颖界面开发

Qt5开发及实例V2.0-第二十二章-Qt.Quick Controls 2新颖界面开发 第22章 Qt Quick Controls 2新颖界面开发22.1 Qt Quick Controls 2简介22.1.1 第一个Qt Quick Controls 2程序22.1.2 Qt Quick Controls 2程序的构成 22.2 Qt Quick Controls 2与1的比较22.2.1 ApplicationWindo…

紫光展锐6nm国产5G处理器T820_国产手机芯片5G方案

紫光展锐T820是一款采用先进6nm EUV工艺的芯片&#xff0c;采用134三丛集八核心CPU架构&#xff0c;由1个主频为 2.7GHz 的 Arm Cortex-A76 大核和 3个主频为2.3GHz 的Arm Cortex-A76大核以及4个主频为2.1GHz的 Arm Cortex-A55组成 &#xff0c;支持高达3MB 三级缓存&#xff0…