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

✨个人主页:bit me👇
✨当前专栏:Java EE初阶👇
✨每日一语:低头赶路,敬事如仪;自知自心,其路则明。
目 录 
- 🍸一. 线程不安全
- 🍹二. 线程不安全的原因
🍸一. 线程不安全
多线程编程,最重要,也是最困难的问题就是线程安全问题,它的万恶之源,罪魁祸首就是调度器的随机调度 / 抢占式执行 这个过程
线程不安全:在随机调度之下,程序执行有多种可能,其中的某些可能导致代码出现了 bug ,线程不安全 / 线程安全问题
例如:两个线程对一个变量进行并发的自增(创建俩线程,让这俩线程同时并发的对一个变量,自增 5w 次,最终预期能一共自增 10w 次)
//创建俩线程,让这俩线程同时并发的对一个变量,自增 5w 次,最终预期能一共自增 10w 次
class Counter{//用来保存计数的变量public int count;public void increase(){count++;}
}
public class Demo14 {//这个实例用来进行累加public static Counter counter = new Counter();public static void main(String[] args) {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.increase();}});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("count: " + counter.count);}
}
运行结果:

再运行一次:

发现随机调度顺序不一样,结果也就不一样
那上面的 bug 如何出现的?
执行一段代码,需要让 CPU 把对应的指令从内存中读取出来,然后再执行
像 count++ 一行代码,对应三个机器指令
- 1.从内存读取数据到 CPU(load)
- 2.在 CPU 寄存器中,完成加法运算(add)
- 3.把寄存器的数据写回到内存中(sava)
指令的排序方式:


上述两种指令的排序方式恰好能到 2 。但是还有许许多多的排列组合方式,就都不一定了。

此时总和就是 1 了

这些还有许许多多就不在此举例了
根据上述的总结,俩种极端情况就是 5w 和 10w 。然后其他的情况就是 5w 和 10w 之间了。
拓展:
操作系统中的随机调度严格意义上来说其实不是 “随机调度” 。在内部他有自己的一套调度过程,我们不需要理解这个过程,了解了也无法改变这个调度。
🍹二. 线程不安全的原因
1. 操作系统的随机调度 / 抢占式执行。【罪魁祸首,万恶之源】
 2. 多个线程修改同一个变量。【三个条件缺一不可,别的情况都没事儿】(所以写代码中可以针对这三个点进行改变进行规避,但是范围有限,不是所有的场景都可以规避掉)
 3. 有些修改操作,不是原子的!【不可拆分的最小单位,就叫原子】
通过 = 来修改,= 只对应一条机器指令,视为是原子的
通过 ++ 来修改,++ 对应三条机器指令,则不是原子的
什么是原子性?
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。(后续详解关于锁)
后果:如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。
4. 内存可见性,引起的线程安全问题。【内存改了,但是在优化的背景下,读不到看不见】
例如一个线程读,一个线程修改:线程 1 LOAD 之后需要进行 TEST ,然后继续 LOAD 再继续 TEST ,这样循环走下去,但是,在程序运行过程中,可能会涉及到一个操作 “优化” (可能是编译器 Javac,也可能是 JVM Java,也可能是操作系统),由于 LOAD 读的操作太慢,反复读,每次读到的数据都是一样的,JVM 就对此做出了优化,不需要重复在内存中读取了,直接就重复用第一次从内存读到寄存器的数据就好了,此时在优化之后,线程 2 突然写了一个数据,由于线程 1 已经优化成读寄存器了,因此线程 2 的修改线程 1 感知不到。
上述优化在单线程环境下没问题,但是多线程情况下就可能产生问题,多线程环境太复杂,编译器/JVM/操作系统进行优化的时候就可能产生误判!!!针对这个问题,Java 引入了 volatile 关键字,让程序猿手动禁止编译器针对某个变量进行上述优化!
5. 指令重排序。【调整代码执行顺序】
也是编译器 / JVM / 操作系统优化
优化在单线程环境下没问题,但是多线程情况下就可能产生问题,例如 Test t = new Test(); 在底层就有三个步骤:
- 创建内存空间
- 往这个内存空间上构造一个对象
- 把这个内存的引用赋值给 t
此处就容易出现指令重排序引入的问题,2 和 3 的顺序是可以调换的,在单线程下调换这俩是没影响的,多线程下就不行了。例如我们俩线程,第一个线程按照先 2 后 3 的顺序,另一个线程尝试读取 t 的引用,当第二个线程读到 t 为非 null 的时候,此时 t 就一定是一个有效对象!!!如果是按照先 3 后 2 的顺序,第二个线程读到 t 为非 null 的时候,仍然可能是一个无效对象!!!
相关文章:
 
多线程的风险 --- 线程安全
✨个人主页:bit me👇 ✨当前专栏:Java EE初阶👇 ✨每日一语:低头赶路,敬事如仪;自知自心,其路则明。 目 录🍸一. 线程不安全🍹二. 线程不安全的原因…...
 
Linux信号详解
文章目录Linux信号什么是信号**从生活角度理解: **技术应用角度的信号进程的注意事项信号概念用kill -l命令可以察看系统定义的信号列表信号处理常见方式概览信号产生通过终端按键产生信号使用signal函数自定义SIGINT信号的处理方式使用sigprocmask函数阻塞2号信号和40号信号vo…...
JAVA使用POI操作EXCEL
设置公式totalRow.createCell(4).setCellFormula("SUM(E9:E35");// 执行公式wb.setForceFormulaRecalculation(true);合并单元格sheet.addMergedRegion(new CellRangeAddress(0, 0, 3, 7));单元格格式CellStyle cellStyle wb.createCellStyle();// 字体XSSFFont fon…...
 
只做笔记有必要买apple pencil吗?苹果笔的代替笔推荐
如果仅仅使用IPAD来进行打游戏和看剧的话,未免有些浪费。ipad的作用还是挺大的,可以用来做学习笔记,也可以用来做绘画,也可以用来做一些重要的内容。很多人都会认为,苹果的电容笔很好用,但是价格上要比一般…...
 
Hive---sqoop安装教程及sqoop操作
sqoop安装教程及sqoop操作 文章目录sqoop安装教程及sqoop操作上传安装包解压并更名添加jar包修改配置文件添加sqoop环境变量启动sqoop操作查看指定mysql服务器数据库中的表在hive中创建一个teacher表跟mysql的mysql50库中的teacher结构相同将mysql中mysql50库中的sc数据导出到h…...
 
【C++】register 关键字
文章目录一. 什么是寄存器?二. 为什么要存在寄存器?三. register 修饰变量一. 什么是寄存器? 我们都知道,CPU主要是负责进行计算的硬件单,但是为了方便运算,一般第一步需要先把数据从内存读取到CPU内&…...
 
剑指 Offer II 024. 反转链表
题目链接 剑指 Offer II 024. 反转链表 easy 题目描述 给定单链表的头节点 head,请反转链表,并返回反转后的链表的头节点。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1] 示例 2: 输入:h…...
 
从Linux内核中学习高级C语言宏技巧
Linux内核可谓是集C语言大成者,从中我们可以学到非常多的技巧,本文来学习一下宏技巧,文章有点长,但耐心看完后C语言level直接飙升。 本文出自:大叔的嵌入式小站,一个简单的嵌入式/单片机学习、交流小站 从…...
详解Python的装饰器
Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里。 为什么需要装饰器 我们假设你的程序实现了say_hello()和say_goodbye()两个函数。 def say_hello():print "hello!"def say_goodbye():print "hello!" # bug hereif…...
 
k8s-Pod域名学习总结
k8s-Pod域名学习总结 大纲 k8s内置DNS服务 配置Pod的域名服务 CornDNS配置 默认Pod的域名 自定义Pod的域名 实战需求 1 Pod有自己的域名 2 集群内部的Pod可以通过域名访问其他的Pod 基础准备: 1 k8s 集群版本1.17 k8s内置DNS服务 k8s1.17安装完成后自动创建…...
 
0405习题总结-不定积分
文章目录1 不定积分的基本概念2 直接积分法-基本积分公式3 第一换元法-凑微分形式法4 第二类换元法5 分部积分求不定积分6 表格法积分7 有理函数求积分后记1 不定积分的基本概念 例1 f(x){x1,x≥012e−x12,x<0求∫f(x)dxf(x) \begin{cases} x1,\quad x\ge0\\ \frac{1}{2}e^…...
QT 常用控件类型命名参考
拟定的QT的控件命名规则:蛇形命名方式 控件类型开头,以下是QT控件类型命名的参考范例 Buttons Buttons起始字符串对象名称举例Push Buttonbuttonbutton_loginTool Buttontool_button / buttonbutton_switchRadio Buttonradio_button / radioradio_boy…...
 
MATLAB与图像处理的那点小事儿~
目录 一、学习内容 二、matlab基本知识 三、线性点运算 四、非线性点运算,伽马矫正 五、直方图 1、直方图均衡化 (1)使用histep函数实现图像均衡化 (2)使用自行编写的均衡化函数实现图像均衡化 2、直方图规定…...
第十四届蓝桥杯模拟赛(第三期)Java组个人题解
第十四届蓝桥杯模拟赛(第三期)Java组个人题解 今天做了一下第三期的校内模拟赛,有些地方不确定,欢迎讨论和指正~ 文章目录第十四届蓝桥杯模拟赛(第三期)Java组个人题解填空题部分第一题【最小数】第二题【E…...
 
Go语言之条件判断循环语句(if-else、switch-case、for、goto、break、continue)
一、if-else条件判断语句 Go中的if-else条件判断语句跟C差不多。但是需要注意的是,Go中强制规定,关键字if和else之后的左边的花括号"{“必须和关键字在同一行,若使用了else if结构,则前段代码快的右花括号”}"必须和关…...
 
深入理解AQS
概念设计初衷:该类利用 状态队列 实现了一个同步器,更多的是提供一些模板方法(子类必须重写,不然会抛错)。 设计功能:独占、共享模式两个核心,state、Queue2.1 statesetState、compareAndSetSta…...
 
JVM学习笔记十:执行引擎
0. 前言 声明: 感谢尚硅谷宋红康老师的讲授。 感谢广大网友共享的笔记内容。 B站:https://www.bilibili.com/video/BV1PJ411n7xZ 本文的内容基本来源于宋老师的课件,其中有一些其他同学共享的内容,也有一些自己的理解内容。 1. …...
 
【2023-03-10】JS逆向之美团滑块
提示:文章仅供参考,禁止用于非法途径 前言 目标网站:aHR0cHM6Ly9wYXNzcG9ydC5tZWl0dWFuLmNvbS9hY2NvdW50L3VuaXRpdmVsb2dpbg 页面分析 接口流程 1.https://passport.meituan.com/account/unitivelogin主页接口:需获取下面的参数࿰…...
全志V853芯片放开快启方案打印及在快起方式下配置isp led的方法
全志V85x芯片 如何放开快启方案的打印? 1.主题 如何放开快启方案的打印 2.问题背景 产品:v851系列快启方案 软件:tina 其他:特有版本信息添加自由描述 (如固件版本,复现概率,特定环境&#x…...
 
大数据 | (一)Hadoop伪分布式安装
大数据原理与应用教材链接:大数据技术原理与应用电子课件-林子雨编著 Hadoop伪分布式安装借鉴文章:Hadoop伪分布式安装-比课本详细 大数据 | (二)SSH连接报错Permission denied:SSH连接报错Permission denied 哈喽&a…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
 
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
 
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
 
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
 
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
 
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
 
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
