多线程---线程安全问题及解决

news2025/2/23 7:04:19

文章目录

  • 一个线程不安全的案例
  • 造成线程不安全的原因
    • 抢占式执行
    • 多个线程修改同一个变量
    • 修改操作不是原子的
    • 内存可见性问题
    • 指令重排序问题
  • 如何让线程变得安全?
    • 加锁
    • volatile

一个线程不安全的案例

题目:有较短时间让变量count从0加到10_0000

解决方案:我们创建两个线程分别让count加5_0000次

结果:count < 10_0000


class Count{
    public int count = 0;
    
    public void increase(){
       count++;
    }

}
public class Demo {
    //验证线程不安全问题
    public static void main(String[] args) {
        Count count1 = new Count();


        // 操作同一个变量
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++){
                count1.increase();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++){
                count1.increase();
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(count1.count);   //  <100000
    }
}

造成线程不安全的原因

抢占式执行

操作系统调度线程的时候,是一个“随机”的过程,当两个线程都参与调度的时候,谁先谁后不确定。

多个线程修改同一个变量

线程之间是“并发执行的”,当多个线程修改同一个变量时,多个线程同时获取到了变量值。某一个线程修改了变量,修改的结果不能被其他线程知道,其他线程还会修改原先获取到的值,导致结果错误。

如果修改的是不同的变量,线程之间独立执行,不会出现问题。

修改操作不是原子的

count++操作底层是三条指令在CPU上完成的:

load:把内存中的值读到CPU寄存器中;
add:count+1;
save:把寄存器的值写回内存

由于这三条指令不是原子的,两个线程在执行时就会有不同的执行顺序:
在这里插入图片描述
在这些执行顺序下,都会使count没有正确的++,使最终结果出错。

内存可见性问题

JVM优化引入的BUG。例如,两个线程在操作同一个变量,一个线程读并且比较,一个线程修改。假设读操作非常频繁的情况下,比较操作也会非常的频繁。但是读是从内存中读,比较是在CPU里比较。比较的速度远远大于读的速度。而且每次读到的值还一样,这时编译器就会大胆优化:只读取一次,后面就不从内存中读了。每次比较都和前面读取到的值比较,不和内存中的值比较。这时另一个线程把内存中的值修改了但是这个线程比较的还时原来的值,就会有问题。

指令重排序问题

JVM优化引入的BUG。由我们自己写的代码在大多数情况下的执行流程中,指令的执行顺序往往都不是最优选择,即没有使运行速度达到最快。因此,JVM在编译时,就会在逻辑等价的前提下,对我们的指令进行重新排序使代码的运行速度变快。

这样的优化在单线程时,是没有问题的。但是在多线程的情况下,线程之间是抢占式执行的,哪条指令先执行哪条指令后执行不确定,就可能有问题。

如何让线程变得安全?

“抢占式执行”是线程调度的基本方式,我们无法干预。

“多个线程修改同一个变量”:我们在特定场景下就是得修改同一个变量,也无法改变。

“操作不是原子的”:我们保证线程安全的主要方式,通过synchronized加锁。

“内存可见性”“指令重排序”:JVM优化的问题。使用volatile解决

加锁

volatile

    public volatile int count = 0;

volatile只有一个用法就是修饰变量,表示该变量的值必须从内存中读取,不能从缓存中读取。
即:volatile禁止了编译器优化,避免了直接读取CPU寄存器中缓存的数据,而是每次都读取内存。

但是volatile并不能保证是操作是原子性的,因此,它只适合用于一个线程读,一个线程修改的场景。不适合用于两个线程都修改的场景。

谈到volatile就会联想到JMM(Java Memory Model):Java内存模型

在JMM中,引入了新的术语:
工作内存(work memory):即CPU寄存器(缓存)
主内存(main memory):真正读取的内存

站在JMM的角度看待volatile:
正常程序的执行过程中,先会把主内存的数据加载到工作内存中,再进行计算处理。编译器优化可能会导致不是每次都会真正的读取主内存,而是直接读取工作内存中的缓存数据,就可能导致内存可见性问题。volatile起到的效果就是保证每次读取数据都是真的从主内存中重新读取。

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

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

相关文章

【AD9361 数字接口CMOS LVDS】A CMOS

〇、综述 本章介绍并行数据端口和串行外设接口&#xff08;SPI&#xff09;&#xff0c;用于在AD9361和BBP之间传输数据和控制/状态信息。 下图显示了这些接口&#xff0c;并提供了AD9361和BBP在宽带无线系统中的使用方式的高级视图。数据接口工作在两种模式之一&#xff1a;标…

LeetCode题:70爬楼梯,126斐波那契数

目录 70&#xff1a;爬楼梯 题目要求&#xff1a; 解题思路&#xff1a;&#xff08;类似斐波那契数&#xff09; 递归解法&#xff1a; 非递归解法&#xff1a; 126&#xff1a;斐波那契数 题目要求&#xff1a; 解题思路&#xff1a; 递归解法&#xff1a; 非递归解…

汇总区间(Java)

大家好我是苏麟 , 这篇文章也是凑数的 ... 描述 : 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 n…

Redis(02)| 数据结构-SDS

一、键值对数据库是怎么实现的&#xff1f; 在开始讲数据结构之前&#xff0c;先给介绍下 Redis 是怎样实现键值对&#xff08;key-value&#xff09;数据库的。 Redis 的键值对中的 key 就是字符串对象&#xff0c;而 value 可以是字符串对象&#xff0c;也可以是集合数据类型…

vue3 + Element-plus + Echarts 5.2 切换不更新、导出PDF不显示 解决方案

vue3 Element-plus Echarts 5.2 切换不更新、导出PDF不显示 解决方案 1、使用 el-tabs 切换导致 Echarts 不显示问题2、折线图 Echarts 不更新问题3、异常抛出&#xff1a; Uncaught (in promise) TypeError: Cannot read properties of undefined (reading ‘type‘)4、Echa…

OpenHarmony docker环境搭建所见的问题和解决

【摘要】OpenHarmony docker环境搭建需要一台安装Ubuntu的虚拟机&#xff0c;并且虚拟机中需要有VScode。 整个搭建流程请参考这篇博客&#xff1a;OpenHarmony docker环境搭建-云社区-华为云 (huaweicloud.com) 上篇博主是用Ubuntu的服务器进行环境搭建的&#xff0c;在使用VS…

基于单片机的智能清洁小车设计—控制系统设计

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、研究的主要内容和目标二、总体方案设计2.1智能清洁小车的硬件系统组成2.2智能清洁小车的硬件结构图 三、 小车结构设计5.1基本布局和功能分析5.2小车二维及三维图小车三维图&#xff1a; 四、 原理图程序 五、…

2.OsgEarth封装

环境&#xff1a;Osg3.6.5 OsgEarth3.2 Qt5.15.2 基于qt将osgEarth封装&#xff0c;在Qt中作为GLWidget进行呈现。 1.Earth类的封装 基于地球的初始化顺序进行了封装&#xff0c;并暴露出了一些必要的属性&#xff0c;类似viwer、map、mapNode等。最为重要的是…

Fourier分析导论——第1章——Fourier分析的起源(E.M. Stein R. Shakarchi)

第 1 章 Fourier分析的起源 (The Genesis of Fourier Analysis) Regarding the researches of dAlembert and Euler could one not add that if they knew this expansion, they made but a very imperfect use of it. They were both persuaded that an arbitrary and d…

基于单片机的空气质量检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 技术交流认准下方 CSDN 官方提供的联系方式 文章目录 概要 一、主要内容二、系统方案设计2.1 系统方案设计2.2 主控制器模块选择 三、 系统软件设计4.1 程序结构分析4.2系统程序…

汇编学习(1)

汇编、CPU架构、指令集、硬编码之间的关系 ● 汇编语言&#xff1a;这是一种低级语言&#xff0c;用于与硬件直接交互。它是由人类可读的机器码或指令组成的&#xff0c;这些指令告诉CPU如何执行特定的任务。每条汇编指令都有一个对应的机器码指令&#xff0c;CPU可以理解和执…

25 行为型模式-备忘录模式

1 备忘录模式介绍 备忘录模式(memento pattern)定义: 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态. 2 备忘录模式原理 3 备忘录模式实现 /*** 发起人角色**/ public class Originator {private Strin…

城中村智能电表改造解决方案

随着我国城市化进程的加快&#xff0c;城中村作为城市发展的矛盾焦点&#xff0c;其居住环境、管理水平等问题日益凸显。城中村用电管理存在着用电安全隐患、电费核算不准确、偷电现象屡禁不止等问题。为了提高城中村用电管理水平&#xff0c;确保用电安全&#xff0c;推进智能…

通过pip,查看tensorflow和tensorflow-probaility版本

查看tensorflow和tensorflow-probability版本 如果在加载tensorflow 和 tensorflow-probablity时&#xff0c;没有成功的话&#xff0c;可以看下这两个包的版本&#xff0c;网上可以搜一下&#xff0c;这两个包版本是否搭配。 从上述信息总可以看到tensorflow包的版本是2.13.0…

DSP开发例程(4): logbuf_print_to_uart

目录 DSP开发例程: logbuf_print_to_uart新建工程源码编辑app.cfgos.cmain.c 调试说明 DSP开发例程: logbuf_print_to_uart SYS/BIOS 提供了 xdc.runtime.Log, xdc.runtime.LoggerBuf 和 xdc.runtime.LoggerSys 这几个模块用于日志记录. 日志信息在 应用程序调试和状态监控中非…

Web服务器与Http协议

Web服务器与Http协议 一.Web服务器 1.简介 Web服务器一般指网站服务器&#xff0c;也称之为WWW(World Wide Web)服务器Web服务器是指驻留于因特网上某种类型计算机的程序Web服务器不是硬件服务器&#xff0c;而是软件服务器。Web服务器其主要功能是提供网上信息浏览服务&…

论文阅读——BERT

ArXiv&#xff1a;https://arxiv.org/abs/1810.04805 github&#xff1a;GitHub - google-research/bert: TensorFlow code and pre-trained models for BERT 一、模型及特点&#xff1a; 1、模型&#xff1a; 深层双向transformer encoder结构 BERT-BASE&#xff1a;(L12, H…

2.19每日一题(分段函数求定积分)

注意&#xff1a;当x>1时需要分区间求定积分 用变上限积分的定理&#xff1a;如果 f(x) 连续&#xff0c;则 F(x) 的导数 f(x) 变上限积分的导数为 f(x) 1、先判断 f(x) 是否连续———>判断在分界点的值是否相等&#xff0c;相等则 f(x) 连续&#xff0c;则 F(x&#…

实体店做商城小程序如何

互联网电商深入各个行业&#xff0c;传统线下店商家无论产品销售还是服务业&#xff0c;仅靠以往的经营模式&#xff0c;很难拓展到客户&#xff0c;老客流失严重&#xff0c;同时渠道单一&#xff0c;无法实现外地客户购物及线上客户赋能等。 入驻第三方平台有优势但也有不足…

Java练习题2020-4

小明今天收了N个鸡蛋&#xff0c;每个鸡蛋各有重量&#xff0c;现在小明想找M个重量差距最小的鸡蛋摆成一盒出售&#xff0c;输出符合条件的最重一盒鸡蛋的总重量 输入说明&#xff1a;第一行&#xff0c;鸡蛋个数N(N<1000) 每盒个数M(M<N)&#xff1b;第二行&#xff0…