JavaEE-线程安全问题
1.线程安全的概念
如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线 程安全的.
为啥会出现线程安全问题?
本质原因: 线程在系统中的调度是无序的/随机的 (抢占式执行).
2.开始说明
先看个线程不安全的例子:
// 线程不安全
class Counter {private int count = 100000;private Object locker = new Object();public void add() {synchronized (locker) {count++;}}public void sub() {count--;}public int get() {return count;}
}public class ThreadDemo13 {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();// 搞两个线程, 两个线程分别对这个 counter 自增 5w 次.Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.sub();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.sub();}});t1.start();t2.start();// 等待两个线程执行结束, 然后看结果.t1.join();t2.join();System.out.println(counter.get());}
}
你觉得结果会是10_0000吗?
测试结果:

这个代码,是两个线程针对同一个变量各自自增 5w 次.
预期结果是 10w,实际结果 像是 个随机值 一样.每次的结果还不一样!!!
实际结果和预期结果不相符,就是 bug!!就是由多线程引起的 bug~~ => 线程不安全 /线程安全问题!
归根结底,线程安全问题,全是因为,线程的无序调度导致了执行顺序不确定,结果就变化了~~
解释下为啥出现这个情况,其实是和线程的调度随机性密切相关.
count++ 操作,本质上是 三个 cpu 指令构成 :
1.load,把内存中的数据读取到 cpu 寄存器中
2.add,就是把寄存器中的值,进行 + 1 运算
3.save,把寄存器中的值写回到 内存中

由于 多线程 调度顺序是不确定的实际执行过程中,这俩线程的 + + 操作实际的指令排列顺序就有很多可能!!!不同的排列顺序下,执行结果,可能是截然不同的!!

此时就发现,按照上述执行过程两个线程自增两次,最后结果是 1,说明 bug 就出现了,其中一次自增的结果,被另一次给覆盖了!!!
由于当前这俩线程调度顺序是无序的,你也不知道这俩线程自增过程中,到底经历了啥.有多少次是“顺序执行”有多少次是“交错执行”不知道!!!得到的结果是啥也就是变化的了~~
线程不安全有以下原因:
1) 线程抢占式执行
2)多个线程修改同一个变量
3) 修改操作不是原子的
4)由于内存可见性,引起的线程不安全
5) 由于指令重排序,引起的线程不安全
其中123比较常见,也是与上述count++例子相关;但是34会在另外的场景涉及,但是和上述count++的例子无瓜.下面会说明.
解释一下1)2)3):
1):由于cpu对于线程的调度是无序的,这也就导致了线程会抢占式执行.这也是会导致线程不安全的最主要原因.
2) :多个线程修改同一个变量 => 是线程不安全的
换言之,一个线程修改/读取同一个变量 =>是线程安全的.
多个线程修改不同变量 => 是线程安全的
多个线程读取同一个变量 =>是线程安全的.
3):如何理解原子性? => 表示不可拆分的最小单位.
比如说,一条 java 语句不一定是原子的,换言之,一条Java语句不一定只是由一条指令构成,而是由多条语句构成如上述count++操作,其实是由3条指会构成:load,add,save.也正因为语句可能不是原子性的,这也就导致了两个线程在抢占式执行的时候,所执行的指令是不符预期的,也就可能会导致不可预期的结果.
那针对上述count++操作所引起的线程不安全问题,能否解决呢?
当然有,那就是让count++操作变成原子的.
Java中使用synchronize关键字进行加锁操作
synchronized 会起到互斥效果,某个线程执行到某个对象的 synchronized 中时其他线程如果也执行到同一个对象 synchronized 就会阻塞等待.

举个栗子:

但是,为了保证线程安全,为了实现原子性,就得利用"锁竞争",也就必须得保证:多个线程是对同一个对象进行加锁.

在上述代码中,这俩线程是在竞争同一个锁对象()counter对象)!
此时就会产生 锁竞争(t1 拿到锁, t2 就得阻塞)此时就可以保证 + + 操作就是原子的,不受影响了!!
由于 t1 已经率先 lock 了t2 再尝试进行 lock就会出现阻塞等待的情况!!此时就可以保证 t2的 load 一定在 t1的 save 之后此时计算的结果就是线程安全的了!!加锁本质上是把并发的变成了串行的!!
synchronize的工作原理以及如何使用synchronize进行加锁:
synchronized 的工作过程:
1.获得互斥锁
2.从主内存拷贝变量的最新副本到工作的内存
3.执行代码
4.将更改后的共享变量的值刷新到主内存
5.释放互斥锁
如何使用synchronize进行加锁
synchronized 要搭配一个具体的对象来使用.

只不过大家要牢记:如果多个线程尝试对同一个锁对象加锁,此时就会产生锁竞争针对不同对象加锁,就不会有锁竞争~
由于内存可见性而引起线程不安全:
先看看场景:
public class ThreadDemo14 {volatile public static int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(() -> {while (flag == 0) {// 空着}System.out.println("循环结束! t1 结束!");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("请输入一个整数: ");flag = scanner.nextInt();});t1.start();t2.start();}
}
预期效果:t1 通过 flag == 0 作为条件进行循环初始情况,将进入循环.
实际效果:输入 非 0的值之后,t1 线程并没有退出.循环没有结束,通过 iconsole 可以看到 t1 线程仍然在执行,处在 RUNNABLE 状态.
为啥有这个问题?
首先需要了解一下内存可见性是个啥内存可见性: 一个线程对共享变量值的修改,能够及时地被其他线程看到.
由于 CPU 与内存之间加入了缓存,在进行数据操作时,先将数据从内存拷贝到缓存中,CPU 直接操作的是缓存中的数据。但在多处理器下,将可能导致各自的缓存数据不一致(这也是可见性问题的由来).
为此,加上volatile关键字进行修饰,就可以保证各个处理器的缓存是一致的.
为什么会不一致呢?这就涉及到了寄存器和缓存了当重复读一个数据的时候,cpu为了提高效率,只会在第一次从内存中读取数据,此后会把数据加载到寄存器里,以后就直接从寄存器里读取数据,就不会再从内存里读取数据了,可是如果此时其他线程对该数据进行修改了,当前线程由于使用"直接复用寄存器的值"的方式,所以感知不到该值已经被修改了.所以对于当前线程而言,此时该cpu向寄存器里读取到的仍然是旧值,也就是无效值.因此导致线程不安全.



此处咱们的处理方式,就是让编译器针对这个场景暂停优化!!
使用volatile关键字,使编译器停止上述优化volatile强制读写内存,
这也就保证了在各个线程里,cpu在向寄存器读取值的时候,都会重新到内存里进行读取,而不会直接复用寄存器里的旧值.同样,在各个线程里,在对某个数据进行修改时,都一定会将修改后的值写回内存,而不会存在"仅在自己的工作内存里进行值的修改,而不会修改主内存里的值”的情况
具体做法: volatile public static int flag =0 ;
加上 volatile 关键字之后,此时编译器就能够保证每次都是重新从内存读取 flag 变量的值.
此时 t2 修改 flag,t1 就可以立即感知到了.t1 就可以正确退出了!!!
由于指令重排序而导致的线程不安全:
指令重排序,也是编译器优化的策略!调整了代码执行的顺序, 让程序更高效!前提也是保证整体逻辑不变!
谈到优化,都得保证 调整之后的结果 和之前是不变的.单线程下容易保证.如果是多线程,就不好说了!

如果是单线程环境此处就可以进行指令重排序:1 肯定是先执行2 和 3,谁先执行,谁后执行,都可以!!
如果是多线程环境下:假设 t1 按照 1 3 2 的顺序执行,当 t1 执行完 13 之后,即将执行 2 的时候,t2 开始执行.由于 t1 的3 已经执行过了,这个引用已经非空了!!!t2 就尝试调用 s.learn0,可是t1还没有该对象进行初始化,此时的 learn 会成啥样,不知道了,很可能产生 bug !!
3.总结
volatile:1)为了保证内存可见性,volatile强制读写内存,保证每次都是从内存中重新读取数据.
2)为了解决在多线程的某些场景下,编译器对代码重排序而导致优化后的程序执行结果和之前不等价"的问题,volatile禁止指今重排序,保证该场景下的某个逻辑按照”本来的指令顺序”执行.
这里再说一下volatile和synchronize 的区别:
共性:volatile与synchronized都用于保证多线程中数据的安全.
区别:(1) volatie通过强制读写内存和禁止指令重排序来保证线程安全。synchronized则是通过对代码块里的语句进行加锁,实现同一时刻只有一个线程能够访问被锁在代码块里的语句,来保证线程安全.
(2) volatile仅能用在变量级别;而synchronized可用在变量和多条语句中.
(3)volatie仅能实现变量操作的内存可见性,无法保证变量操作的原子性;而synchronized可以实现变量操作的内存可见性与原子性.
volatile 属性的读写操作都是无锁的,它不能替代 synchronized,因为它没有无法保证原子性.因为无锁,不需要花费时间在获取锁和释放锁上,也不会导致线程阻塞,所以volatile比synchronize更轻量.

uu们加油呀!!!

相关文章:
JavaEE-线程安全问题
1.线程安全的概念 如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线 程安全的. 为啥会出现线程安全问题? 本质原因: 线程在系统中的调度是无序的/随机的 (抢占式执行). 2.开始说明 先看个线程不安全的例子…...
【Node.js】身份认证,Cookie和Session的认证机制,express中使用session认证和JWT认证
Node.jsWeb开发模式如何选择Web开发模式身份认证什么是身份认证为什么要身份认证不同开发模式的身份认证Session认证机制提高身份认证的安全性Session的工作原理Express中使用Session认证Session认证机制的局限性JWT认证机制JWT的工作原理JWT的组成部分Express中使用JWT在登录成…...
Redis删除策略和淘汰策略
一、删除策略 删除策略就是针对已过期数据的处理策略。 针对过期数据要进行删除的时候都有哪些删除策略呢? 1.定时删除2.惰性删除3.定期删除1、立即删除 当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作。 优…...
LFM雷达实现及USRP验证【章节2:LFM雷达测距】
目录 1. 参数设计 几个重要的约束关系 仿真参数设计 2. matlab雷达测距代码 完整源码 代码分析 回顾:LFM的基本原理请详见第一章 本章节将介绍LFM雷达测距的原理及实现 1. 参数设计 几个重要的约束关系 带通采样定理: 因此如果我们B80MHz时&a…...
菜鸟刷题Day5
⭐作者:别动我的饭 ⭐专栏:菜鸟刷题 ⭐标语:悟已往之不谏,知来者之可追 一.一维数组的动态和:1480. 一维数组的动态和 - 力扣(LeetCode) 描述 给你一个数组 nums 。数组「动态和」的计算公式…...
已解决AttributeError:module tensorflow no attribute app异常的正确解决方法,亲测有效!!!
已解决AttributeError:module tensorflow no attribute app异常的正确解决方法,亲测有效!!! 文章目录报错问题解决方法福利报错问题 粉丝群里面的一个小伙伴敲代码时发生了报错(当时他心里瞬间凉了一大截&…...
Hadoop集群环境配置搭建
一、简单介绍 Hadoop最早诞生于Cutting于1998年左右开发的一个全文文本搜索引擎 Lucene,这个搜索引擎在2001年成为Apache基金会的一个子项目,也是 ElasticSearch等重要搜索引擎的底层基础。 项目官方:https://hadoop.apache.org/ 二、Linux环…...
Thread类的基本用法
Thread类的基本用法🔎1.线程创建🌻继承Thread类🌼继承Thread重写run()方法🌼继承Thread匿名内部类🌻实现Runnable接口🌼实现Runnable接口重写run()方法🌼实现Runnable接口匿名内部类ἳ…...
YOLOV8改进:如何增加注意力模块?(以CBAM模块为例)
YOLOV8改进:如何增加注意力模块?(以CBAM模块为例)前言YOLOV8nn文件夹modules.pytask.pymodels文件夹总结前言 因为毕设用到了YOLO,鉴于最近V8刚出,因此考虑将注意力机制加入到v8中。 YOLOV8 代码地址&am…...
Spark Streaming DStream的操作
一、DStream的定义 DStream是离散流,Spark Streaming提供的一种高级抽象,代表了一个持续不断的数据流。DStream可以通过输入数据源来创建,比如Kafka、Flume,也可以通过对其他DStream应用高阶函数来创建,比如map、redu…...
蓝桥杯冲刺 - week1
文章目录💬前言🌲day192. 递归实现指数型枚举843. n-皇后问题🌲day2日志统计1209. 带分数🌲day3844. 走迷宫1101. 献给阿尔吉侬的花束🌲day41113. 红与黑🌲day51236. 递增三元组🌲day63491. 完全…...
Leetcode27. 移除元素
目录一、题目描述:二、解决思路和代码1. 解决思路2. 代码一、题目描述: 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用…...
ViewService——一种保证客户端与服务端同步的方法
简介在分布式系统中,最常见的场景就是主备架构。但是如果主机不幸宕机,如何正确的通知客户端当前后端服务器的状况成为一个值得研究的问题。本文描述了一种简单的模型用于解决此问题。背景以一个分布式的Key-Value数据库为背景。数据库对外提供3个接口Ge…...
使用STM32F103ZE开发贪吃蛇游戏
目录 前言 一、设置FreeROTS用户任务 (1)事件event任务 (2)按键输入方向控制任务 (3)果实食物任务 (4)显示任务函数 (3)开始任务 二、主函数 三、ADC采样…...
如何利用Web3D技术打造在线虚拟展览馆
随着Web3D技术的不断发展,越来越多的企业和组织开始将其应用于虚拟展览馆的建设中。虚拟展览馆可以为观众提供高度沉浸式的展览体验,让观众可以随时随地参观各种展览,同时也为展览组织者提供了更多的展示方式和机会。下面将介绍如何利用Web3D…...
第二十三章 opengl之高级OpenGL(实例化)
OpenGL实例化实例化数组绘制小行星带实例化 综合应用。 如果绘制了很多的模型,但是大部分的模型包含同一组顶点数据,只是不同的世界空间变换。 举例:一个全是草的场景,每根草都是一个包含了几个小三角形的模型。需要绘制很多根草…...
C++ String类总结
头文件 #include <string>构造函数 default (1) basic_string();explicit basic_string (const allocator_type& alloc); copy (2) basic_string (const basic_string& str);basic_string (const basic_string& str, const allocator_type& alloc); su…...
内网升级“高效安全”利器!统信软件发布私有化更新管理平台
随着数字化的深度推进,信息安全重要性进一步凸显。建设自主可控的国产操作系统,提升信息安全自主能力,已成为国家重要战略之一。 操作系统安全对计算机系统的整体安全发挥着关键作用,各类客户往往需要在第一时间获取更新与安全补…...
JAVA开发(自研项目的开发与推广)
https://live.csdn.net/v/284629 案例背景: 作为JAVA开发人员,我们可以开发无数多的web项目,电商系统,小程序,H5商城。有时候作为技术研发负责人,项目做成了有时候也需要对内进行内测,对外进行…...
Mysql用户权限分配详解
文章目录MySQL 权限介绍一、Mysql权限级别分析(1)全局级别(1.1) USER表的组成结构(1.1.1) 用户列(1.1.2) 权限列(1.1.3) 安全列(1.1.4)…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能
指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...
Linux基础开发工具——vim工具
文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...
如何做好一份技术文档?从规划到实践的完整指南
如何做好一份技术文档?从规划到实践的完整指南 🌟 嗨,我是IRpickstars! 🌌 总有一行代码,能点亮万千星辰。 🔍 在技术的宇宙中,我愿做永不停歇的探索者。 ✨ 用代码丈量世界&…...
嵌入式面试常问问题
以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...
【threejs】每天一个小案例讲解:创建基本的3D场景
代码仓 GitHub - TiffanyHoo/three_practices: Learning three.js together! 可自行clone,无需安装依赖,直接liver-server运行/直接打开chapter01中的html文件 运行效果图 知识要点 核心三要素 场景(Scene) 使用 THREE.Scene(…...
