线程安全问题的原因与解决方案

news2024/11/19 7:47:00

目录

1.线程在系统中是随机调度,抢占式执行;

2.多个线程同时修改同一个变量

3.线程对变量的修改操作指令不是“原子”

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

拓展

小结

5.指令重排序,引起的线程不安全


为了可以更好的解释,给大家看一个错误的代码,问题是两个线程修改同一个变量,每个线程每次对变量+1循环50000次;测试发现,结果都是错误的;

public class demo1 {
    private static int count=0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    count++;
                }
            }
        };
        Thread t2=new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    count++;
                }
            }
        };
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}
1.线程在系统中是随机调度,抢占式执行;

可以说这是线程出现安全问题的罪魁祸首,但这是我们无法干预的,这是系统规定好的原则;

2.多个线程同时修改同一个变量

无论是一个线程修改一个变量,一个线程读取一个变量,还是多个线程读取同一个变量都没事,都不会出现安全问题,当多个线程同时修改同一个变量就可能会出现安全问题。String对象不可修改,就是要在一定程度上保证String对象的安全;

那我们就可以不要多个线程同时修改一个变量,从而来规避线程不安全的问题,但是有些时候我们就不等不同时用多个线程来修改同一个变量;

3.线程对变量的修改操作指令不是“原子”

就像上面例子中所说的对一个变量进行++修改,这样一个修改操作需要三个指令,load、add、save,三个指令,所以两个线程的指令在系统的执行又是随机调度的,就会产生无数种错误的情况;

针对以上2,3的原因,一般对修改操作采用加锁的策略,对以上代码进行修改,执行结果是100000;

public class demo1 {
    private static int count=0;
    public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        Thread t1=new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    synchronized (lock){
                        count++;
                    }
                }
            }
        };
        Thread t2=new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    synchronized (lock){
                        count++;
                    }
                }
            }
        };
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}
4.内存可见性,引起的线程不安全

先看一个例子

import java.util.Scanner;
public class demo1 {
    private static int count=0;
    public static void main(String[] args) {
        Thread t1=new Thread(){
            @Override
            public void run() {
                while(count==0){

                }
            }
        };
        Thread t2=new Thread(){
            @Override
            public void run() {
                Scanner sc=new Scanner(System.in);
                count=sc.nextInt();
            }
        };
        t1.start();
        t2.start();
    }
}

线程t1是循环只要count!=0就结束,t2修改count的值,预期效果:输入非0的值的时候,线程t1就会结束,整个程序也就会结束,但实际当输入1时,线程t1不结束,程序也不结束;原因如下:

while循环的指令可以分成两个:

  1. load:将count的值从内存加载到cpu寄存器上,
  2. cmp 将count的值与0作比较

由于指令1是将数据从内存加载到寄存器上,而指令2只是在寄存器上作比较,两个指令的执行速度差了几个数量级;在上述执行过程中,load一次的时间,cmp可以执行成千上万遍,而且jvm还发现每次load的结果都一样,所以,jvm就会将上述代码优化,只有第一次load的时候才是真正从内存加载到寄存器,后面的每一次load都是不是真正的load,而是直接读取第一次load加载到寄存器上的值,使代码执行得更快;

这就导致后续线程2修改了count的值,修改的是内存上count的值,但是线程1已经不会再去内存上读取count,而是直接读取了寄存器上的值,所以线程1依旧在执行while循环;

优化,是为代码执行得更快,但前提是优化前后的逻辑不会改变,值得注意的是在单线程中jvm的优化基本是不会有问题,在多线程中就需要注意了

为了解决上述多线程中的内存可见性完全问题,java引入了volatile关键字,它的作用就是提示编译器这个变量后续可能会进行修改,不需要编译优化;

 private static volatile int count=0;
拓展

有趣的是,如果在while循环中打印一句话,代码就会如预期效果一样,原因就是:如果循环体有IO操作或者阻塞,这就会使循环体的执行速度大幅度下降,且这个IO操作的执行速度甚至要比上面的load执行要慢很多个数量级,这时候jvm就会觉得优化掉load没有必要而不去优化掉load指令了;

小结

上述问题的根本还是编译器优化引起的,jvm优化掉load,线程2修改count的值,线程1无法感知,这也就是所谓的 内存可见性问题;

5.指令重排序,引起的线程不安全

指令重排序,顾名思义就是指令重新排序而导致的线程安全,这仍然是一个又编译器优化导致的问题,且在单线程发生指令重排序是安全,但是多线程发生指令重排序可能就会出现问题了;

你要在一个市场里鸡蛋,盐,番茄,猪肉,但是在不同的地方,会发生一下两种情况,无疑发生指令重排序确实是可以优化了购买过程且保证线程是安全的;

直接下看多线程中,指令重排序是如何可能造成线程不安全的,这里需要先去了解一下单例模式中的懒汉模式,我会结合该模式进行分享,两种单例模式(保证线程安全)这是小生写的有关单例模式的博客

在看代码之前,先说明一下,我们把代码  singletonLazy=new SingletonLazy() 大致分成三个指令,其实远不止三个指令,这里是为了更好说明,三个指令分别是:·1.申请内存空间 2.调用构造方法 3.把此时内存空间的地址,赋值给singletonLazy引用;在编译器优化,指令重排序的情况下,上述过程有可能是1 2 3 ,也有可能是1 3 2,但是1一定先执行的,下面例子就按照 1 3 2的可能进行分析:

if(singletonLazy==null){
            synchronized (singletonLazy){
                if(singletonLazy==null){
                    singletonLazy=new SingletonLazy();
                }
            }
        }
return singletonLazy;

 以上为假设两个线程的代码执行顺序,从左到右,从上到下,在t1线程的指令3执行结束之后,轮到t2执行,由于此时singletonLazy不是null,所以t2线程会直接返回一个指向内存均为0的空间,以至于后面的线程访问该唯一实例化对象是会发生错误,从而引发线程不安全;

其实代码是因为双重否定造成的线程不安全,如果没有外层的if判断,t2执行到锁的时候就会阻塞等待线程1的结束,也就不会有上述线程安全问问题了,但是该单例模式的代码是不能改变的;

解决方法:用volatile关键字修饰改变量,volatile不仅能针对内存可见性的多线程安全问题,还能禁止针对修改变量可能发生的指令重排序而导致线程安全问题,一个代码中会有很多地方发生指令重排序,这是由于编译器优化,volatile只能保证修饰的变量的读写操作的指令不会发生指令重排序;

private static volatile SingletonLazy singletonLazy=null;

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

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

相关文章

儿童运动app开发,赢在“起跑线”

我国很早就发出了“体育强国”的口号&#xff0c;随着新高考的改革&#xff0c;体育成绩的占比也越来越大&#xff0c;这对同学们的高考分数也发挥着不可小觑的作用。为此&#xff0c;很多家长开始从小培养孩子的体育运动&#xff0c;力求在强身健体的同时&#xff0c;为考试助…

【大数据】大数据平台建设整体解决方案(Word源文件)

建设大数据管理中台&#xff0c;按照统一的数据规范和标准体系&#xff0c;构建统一数据采集&#xfe63;治理&#xfe63;共享标准、统一技术开发体系、统一接口 API &#xff0c;实现数据采集、平台治理&#xff0c;业务应用三层解耦&#xff0c;并按照统一标准格式提供高效的…

PMP考试难度大吗?没基础能过吗?

由于目前的PMP考试主要基于新的大纲&#xff0c;许多内容已经发生了变化&#xff0c;因此学习新的知识&#xff0c;适应新的变化仍然非常重要。 一、新版考试的题量和答题时间有什么变化&#xff1f; 总题量从200道减少到180道&#xff0c;因此答题时间相对变得更充裕一些。 …

计算机网络:运输层 - TCP首部格式 连接的创建与释放

计算机网络&#xff1a;运输层 - TCP首部格式 & 连接的创建与释放 TCP首部格式源端口 目的端口序号确认号数据偏移保留控制位窗口检验和紧急指针 TCP连接创建 - 三次握手TCP传输过程TCP连接释放 - 四次挥手 TCP首部格式 TCP的首部如下&#xff1a; 首部的前20 byte是固定的…

在Python项目中自定义日志工具

在Python项目中自定义日志工具 日志记录是软件开发中的一个关键部分&#xff0c;它可以帮助开发人员调试代码、监控运行状况以及记录重要事件。在Python中&#xff0c;logging 模块提供了强大的日志记录功能。本文将介绍如何创建一个日志工具&#xff0c;使其能够同时将日志输…

【会议征稿,IEEE出版】第四届电气工程与机电一体化技术国际学术会议(ICEEMT 2024,7月5-7)

第四届电气工程与机电一体化技术国际学术会议&#xff08;ICEEMT 2024&#xff09;定于2024年7月5-7日在浙江省杭州市隆重举行 。会议主要围绕“电气工程”、“机电一体化” 等研究领域展开讨论&#xff0c;旨在为电气工程、机电一体化等领域的专家学者、工程技术人员、技术研发…

leetcode (top100)接雨水

题目&#xff1a; 题解&#xff1a; 可以把每个宽度看作一个桶&#xff0c;每个桶能接的水等于这个桶左右两个方向最高桶的最小高度再减去这个桶本身的高度。把每个桶能接的水相加即可。 难点在于如何快速找到当前桶的左右两个方向的最高桶的高度&#xff0c;可以先遍历一遍…

Java学习【深入解读File类:从基础到高级的完整指南】

Java学习【深入解读File类&#xff1a;从基础到高级的完整指南】 ⭐File的概述和构造方法⭐File常见的成员方法&#x1f319;构造方法&#x1f319;length()&#x1f319;getAbsoluteFile()和getPath()&#x1f319;getName()和lastModified() ⭐文件的创建和删除⭐获取并遍历 …

电脑操作系统有哪些?一文了解全部!

在当今数字化飞速发展的时代&#xff0c;电脑操作系统作为计算机硬件与软件之间的桥梁&#xff0c;扮演着至关重要的角色。它不仅管理着计算机的各项资源&#xff0c;还为用户提供了一个直观、友好的界面&#xff0c;使得用户可以轻松地进行各种操作和任务。那么&#xff0c;电…

【八股系列】介绍React高阶组件,适用于什么场景?

文章目录 1. HOC的工作原理2. 返回的新组件3. 适用场景4. 注意事项5. 示例代码 React高阶组件&#xff08; Higher-Order Components&#xff0c;简称HOC&#xff09;是 React中一种高级的 复用组件逻辑的技术。 HOC自身不是 React API的一部分&#xff0c;而是基于 Reac…

Zyxel NAS设备 setCookie 未授权命令注入漏洞复现(CVE-2024-29973)

0x01 产品简介 Zyxel-NAS 是指由 Zyxel Communications Corporation(合勤科技股份有限公司)开发和生产的网络附加存储(Network Attached Storage,简称 NAS)设备。NAS 是一种专门用于存储和共享文件的设备,它通过网络连接到计算机、服务器或其他设备,提供集中式的文件存…

linux最大线程数限制及打开最大文件数

1.root用户下执行 ulimit -a 然后查看 max user processes 这个值通常是系统最大线程数的一半 max user processes&#xff1a;当前用户同时打开的进程(包括线程)的最大个数为 2.普通用户下 ulimit -a 出现的max user processes的值 默认是 /etc/security/limits.d/20-nproc.co…

范式(下)-BC范式(BCNF)、关系模式的规范化

一、关系模式STC 假设有一个关系模式STC&#xff0c;包含有学号Sno、教师编号Tno、课程编号Cno、选课成绩G四个属性 即STC(Sno&#xff0c;Tno&#xff0c;Cno&#xff0c;G) 数据间的关系为 每个学生可选修多门课程&#xff0c;每门课程可以被多名学生选修每个老师只能讲授…

虹科免拆诊断案例 | 15款马自达3偶发高速CAN网络故障

CAN总线大家都不陌生&#xff0c;遇到这类故障&#xff0c;我们第一反应可能是用万用表测一下电压&#xff0c;看看存不存在短路、断路等问题。不过&#xff0c;使用万用表测得的电压值只是CAN总线上的平均电压&#xff0c;是一个大概的数值&#xff0c;并不能反映真实的CAN总线…

用户组相关的一些命令

1./etc/passwd>列出了系统里的所有用户 2./etc/shadow>保存了用户的密码信息 空表示密码为空 *表示账号被在锁定 !表示未设置密码 !!表示这个密码已经过期 $1表示MD5加密&#xff0c; $2表示用Blowfish加密&#xff0c; $5表示用SHA-256加密&#xff0c; $6表示SHA-512加…

关于微信小程序(必看)

前言 为规范开发者的用户个人信息处理行为&#xff0c;保障用户的合法权益&#xff0c;自2023年9月15日起&#xff0c;对于涉及处理用户个人信息的小程序开发者&#xff0c;微信要求&#xff0c;仅当开发者主动向平台同步用户已阅读并同意了小程序的隐私保护指引等信息处理规则…

FANUC喷涂机器人P-350iA电机过热维修解决方案

发那科喷涂机器人作为自动化喷涂生产线的重要组成部分&#xff0c;其性能稳定性和可靠性对于生产效率和产品质量具有重要影响。然而&#xff0c;在实际使用过程中&#xff0c;FANUC喷涂机器人P-350iA电机过热故障问题往往成为影响其正常运行的主要因素之一。 FANUC机器人M-100…

数据库实战(一)(关系数据库设计)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;数据库 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 前言 练习题 题型一&#xff1a;判断关系…

登录爆破神器:Hydra 保姆级教程

前言 Hydra 是一个广泛使用的网络登录破解工具&#xff0c;支持多种协议&#xff08;如 HTTP、FTP、SSH、Telnet 等&#xff09;&#xff0c;用于执行暴力破解攻击。它由 Van Hauser 和 The Hackers Choice (THC) 团队开发&#xff0c;设计用于快速和高效地尝试大量用户名和密…

信息技术课如何禁止学生玩游戏

在信息技术课上禁止学生玩游戏是一个常见的挑战&#xff0c;但可以通过一系列策略和工具来有效地实现。以下是一些建议&#xff1a; 明确课堂规则和纪律&#xff1a; (1)在课程开始时&#xff0c;明确告知学生课堂规则和纪律&#xff0c;包括禁止玩游戏的规定。 (2)强调遵守…