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

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.线程安全的概念 如果多线程环境下代码运行的结果是符合我们预期的&#xff0c;即在单线程环境应该的结果&#xff0c;则说这个程序是线 程安全的. 为啥会出现线程安全问题? 本质原因: 线程在系统中的调度是无序的/随机的 (抢占式执行). 2.开始说明 先看个线程不安全的例子…...

【Node.js】身份认证,Cookie和Session的认证机制,express中使用session认证和JWT认证

Node.jsWeb开发模式如何选择Web开发模式身份认证什么是身份认证为什么要身份认证不同开发模式的身份认证Session认证机制提高身份认证的安全性Session的工作原理Express中使用Session认证Session认证机制的局限性JWT认证机制JWT的工作原理JWT的组成部分Express中使用JWT在登录成…...

Redis删除策略和淘汰策略

一、删除策略 删除策略就是针对已过期数据的处理策略。 针对过期数据要进行删除的时候都有哪些删除策略呢&#xff1f; 1.定时删除2.惰性删除3.定期删除1、立即删除 当key设置有过期时间&#xff0c;且过期时间到达时&#xff0c;由定时器任务立即执行对键的删除操作。 优…...

LFM雷达实现及USRP验证【章节2:LFM雷达测距】

目录 1. 参数设计 几个重要的约束关系 仿真参数设计 2. matlab雷达测距代码 完整源码 代码分析 回顾&#xff1a;LFM的基本原理请详见第一章 本章节将介绍LFM雷达测距的原理及实现 1. 参数设计 几个重要的约束关系 带通采样定理&#xff1a; 因此如果我们B80MHz时&a…...

菜鸟刷题Day5

⭐作者&#xff1a;别动我的饭 ⭐专栏&#xff1a;菜鸟刷题 ⭐标语&#xff1a;悟已往之不谏&#xff0c;知来者之可追 一.一维数组的动态和&#xff1a;1480. 一维数组的动态和 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个数组 nums 。数组「动态和」的计算公式…...

已解决AttributeError:module tensorflow no attribute app异常的正确解决方法,亲测有效!!!

已解决AttributeError&#xff1a;module tensorflow no attribute app异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 文章目录报错问题解决方法福利报错问题 粉丝群里面的一个小伙伴敲代码时发生了报错&#xff08;当时他心里瞬间凉了一大截&…...

Hadoop集群环境配置搭建

一、简单介绍 Hadoop最早诞生于Cutting于1998年左右开发的一个全文文本搜索引擎 Lucene&#xff0c;这个搜索引擎在2001年成为Apache基金会的一个子项目&#xff0c;也是 ElasticSearch等重要搜索引擎的底层基础。 项目官方&#xff1a;https://hadoop.apache.org/ 二、Linux环…...

Thread类的基本用法

Thread类的基本用法&#x1f50e;1.线程创建&#x1f33b;继承Thread类&#x1f33c;继承Thread重写run()方法&#x1f33c;继承Thread匿名内部类&#x1f33b;实现Runnable接口&#x1f33c;实现Runnable接口重写run()方法&#x1f33c;实现Runnable接口匿名内部类&#x1f33…...

YOLOV8改进:如何增加注意力模块?(以CBAM模块为例)

YOLOV8改进&#xff1a;如何增加注意力模块&#xff1f;&#xff08;以CBAM模块为例&#xff09;前言YOLOV8nn文件夹modules.pytask.pymodels文件夹总结前言 因为毕设用到了YOLO&#xff0c;鉴于最近V8刚出&#xff0c;因此考虑将注意力机制加入到v8中。 YOLOV8 代码地址&am…...

Spark Streaming DStream的操作

一、DStream的定义 DStream是离散流&#xff0c;Spark Streaming提供的一种高级抽象&#xff0c;代表了一个持续不断的数据流。DStream可以通过输入数据源来创建&#xff0c;比如Kafka、Flume&#xff0c;也可以通过对其他DStream应用高阶函数来创建&#xff0c;比如map、redu…...

蓝桥杯冲刺 - week1

文章目录&#x1f4ac;前言&#x1f332;day192. 递归实现指数型枚举843. n-皇后问题&#x1f332;day2日志统计1209. 带分数&#x1f332;day3844. 走迷宫1101. 献给阿尔吉侬的花束&#x1f332;day41113. 红与黑&#x1f332;day51236. 递增三元组&#x1f332;day63491. 完全…...

Leetcode27. 移除元素

目录一、题目描述&#xff1a;二、解决思路和代码1. 解决思路2. 代码一、题目描述&#xff1a; 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用…...

ViewService——一种保证客户端与服务端同步的方法

简介在分布式系统中&#xff0c;最常见的场景就是主备架构。但是如果主机不幸宕机&#xff0c;如何正确的通知客户端当前后端服务器的状况成为一个值得研究的问题。本文描述了一种简单的模型用于解决此问题。背景以一个分布式的Key-Value数据库为背景。数据库对外提供3个接口Ge…...

使用STM32F103ZE开发贪吃蛇游戏

目录 前言 一、设置FreeROTS用户任务 &#xff08;1&#xff09;事件event任务 &#xff08;2&#xff09;按键输入方向控制任务 &#xff08;3&#xff09;果实食物任务 &#xff08;4&#xff09;显示任务函数 &#xff08;3&#xff09;开始任务 二、主函数 三、ADC采样…...

如何利用Web3D技术打造在线虚拟展览馆

随着Web3D技术的不断发展&#xff0c;越来越多的企业和组织开始将其应用于虚拟展览馆的建设中。虚拟展览馆可以为观众提供高度沉浸式的展览体验&#xff0c;让观众可以随时随地参观各种展览&#xff0c;同时也为展览组织者提供了更多的展示方式和机会。下面将介绍如何利用Web3D…...

第二十三章 opengl之高级OpenGL(实例化)

OpenGL实例化实例化数组绘制小行星带实例化 综合应用。 如果绘制了很多的模型&#xff0c;但是大部分的模型包含同一组顶点数据&#xff0c;只是不同的世界空间变换。 举例&#xff1a;一个全是草的场景&#xff0c;每根草都是一个包含了几个小三角形的模型。需要绘制很多根草…...

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…...

内网升级“高效安全”利器!统信软件发布私有化更新管理平台

随着数字化的深度推进&#xff0c;信息安全重要性进一步凸显。建设自主可控的国产操作系统&#xff0c;提升信息安全自主能力&#xff0c;已成为国家重要战略之一。 操作系统安全对计算机系统的整体安全发挥着关键作用&#xff0c;各类客户往往需要在第一时间获取更新与安全补…...

JAVA开发(自研项目的开发与推广)

https://live.csdn.net/v/284629 案例背景&#xff1a; 作为JAVA开发人员&#xff0c;我们可以开发无数多的web项目&#xff0c;电商系统&#xff0c;小程序&#xff0c;H5商城。有时候作为技术研发负责人&#xff0c;项目做成了有时候也需要对内进行内测&#xff0c;对外进行…...

Mysql用户权限分配详解

文章目录MySQL 权限介绍一、Mysql权限级别分析&#xff08;1&#xff09;全局级别&#xff08;1.1&#xff09; USER表的组成结构&#xff08;1.1.1&#xff09; 用户列&#xff08;1.1.2&#xff09; 权限列&#xff08;1.1.3&#xff09; 安全列&#xff08;1.1.4&#xff09…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

JVM虚拟机:内存结构、垃圾回收、性能优化

1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

站群服务器的应用场景都有哪些?

站群服务器主要是为了多个网站的托管和管理所设计的&#xff0c;可以通过集中管理和高效资源的分配&#xff0c;来支持多个独立的网站同时运行&#xff0c;让每一个网站都可以分配到独立的IP地址&#xff0c;避免出现IP关联的风险&#xff0c;用户还可以通过控制面板进行管理功…...

Qt 事件处理中 return 的深入解析

Qt 事件处理中 return 的深入解析 在 Qt 事件处理中&#xff0c;return 语句的使用是另一个关键概念&#xff0c;它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别&#xff1a;不同层级的事件处理 方…...

用鸿蒙HarmonyOS5实现中国象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...

Linux中《基础IO》详细介绍

目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改&#xff0c;实现简单cat命令 输出信息到显示器&#xff0c;你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...