【多线程带来的的风险-线程安全的问题的简单实例-线程不安全的原因】

文章目录

  • 前言
  • 线程不安全的5大原因
    • 1. 抢占式执行和随机调度
    • 2. 多个线程同时修改一个变量(共享数据)
    • 3. 修改操作不是原子性的
    • 4. 内存可见性
    • 5. 指令重排序


前言

什么是线程安全?

简单来说,如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

但是在多线程的环境下,我们很难预知线程的调度方法,这就像A和B两个施工队,同时在山的两头开始挖隧道,理想状态下我们希望他们能够在中间相遇,但是也极有可能他们没有相遇,各自挖了一条隧道。


提示:以下是本篇文章正文内容,下面案例可供参考

线程不安全的5大原因

1. 抢占式执行和随机调度

线程的抢占式执行

  • 是指操作系统可以在任何时刻强制暂停当前线程的执行,并将处理器分配给另一个就绪状态的线程。抢占式执行可以保证操作系统的响应能力和调度公平性,避免某个线程长时间占用处理器而导致其他线程无法得到执行的问题。

线程的随机调度

  • 是指操作系统在多个就绪状态的线程中随机选择一个线程来执行。随机调度可以保证线程执行的公平性和可预测性,避免某个线程过度优先导致其他线程无法得到执行的问题。

简单来说就是,线程中的代码执行到任意的一行,都随时可能被切换出去。

代码示例

public class Ceshi {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i <= 10; i++) {
                System.out.print(i + " ");
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 11; i <= 20; i++) {
                System.out.print(i + " ");
            }
        });
        //我们期望能够打印0-20的递增形式
        t1.start();
        t2.start();
    }
}

输出结果 1

在这里插入图片描述

输出结果 2
在这里插入图片描述

输出结果 3
在这里插入图片描述

图解

在这里插入图片描述

可以看出每次执行的结束都不相同,这是由于线程的抢占式执行和随机调度的结果,你无法预测操作系统会如何安排任务的执行顺序。


2. 多个线程同时修改一个变量(共享数据)

代码示例

public class Ceshi {
    public static int a = 0;//共享数据a

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                a += 2;
                if (a > 0) System.out.print("大 ");
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                a -= 3;
                if (a < 0) System.out.print("小 ");
            }
        });
        t1.start();
        t2.start();
        //保证t线程能够执行完毕
        Thread.sleep(1000);
        System.out.println();
    }
}

输出结果 1

在这里插入图片描述

输出结果 2

在这里插入图片描述

输出结果 3

在这里插入图片描述

图解

在这里插入图片描述

t1 t2 这两个线程都能够访问到a,两个线程同时分别对a进行修改和判断,你无法预料到它会被两个线程如何的互相争夺。


3. 修改操作不是原子性的

代码示例

class Counter1 {
    private int count = 0;

    public void add() {
        count++;
        // ++ 操作就不是原子性的
        // 它会被操作系统分为三步操作
        //1. load,把内存中的数据读取到cpu寄存器中
        //2. add,把寄存器中的值,进行+1运算
        //3. save,把寄存器中的值写回到内存中
    }

    public int get() {
        return count;
    }
}

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        //两个线程,分别对count++
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        t1.start();
        t2.start();


        t1.join();
        t2.join();

        //与预期结果100000不同(线程不安全问题,抢占式执行)
        System.out.println(counter.get());
    }
}

输出结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

图解

在这里插入图片描述

4. 内存可见性

可见性指, 一个线程对共享变量值的修改,能够及时地被其他线程看到

在这里插入图片描述

如果a不是内存可见的,那么t1t2就会同时对a进行修改,可能会对预期的结果产生问题。


5. 指令重排序

指令重排序是现代处理器为了提高指令执行效率所采取的一种优化手段。它可以将指令的执行顺序进行重新排序,以最大程度地利用CPU内部资源,提高CPU的执行效率。

具体来说,指令重排序可以分为两种类型:

  1. 编译器重排序:编译器会将乱序的代码重新排列成一个顺序执行代码,以提高程序执行速度。

  2. 处理器重排序:处理器会按照一定的策略对指令执行顺序进行调整,以最大程度地利用CPU内部的各种功能单元。

代码示例

public class Ceshi {
    public static int flag = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (flag == 0) {
                // == 符号分为两部
                //1. load,开销是cmp的几千倍
                //2. cmp,很快
                //load之前,已经cmp了很多次,编译器就认为每次load的值都相同,为了节省时间,所以直接将load优化掉了
                //就导致结果出错
                //一般单线程都是正确的,多线程会出错

                // 空循环,会快速的执行,发现每次flag==0,此时编译器就会动了优化的心思
            }
            //导致该句,输出不来
            System.out.println("循环结束,t1结束");
        });
        Thread t2 = new Thread(() -> {
        	while (true) {
            	Scanner scanner = new Scanner(System.in);
            	System.out.println("请输入一个整数改变flag:");
            	//输入的这点时间内,flag==0可能已经比较了无数次
            	flag = scanner.nextInt();
            }
        });

        t1.start();
        t2.start();

    }
}

输出结果

在这里插入图片描述

可以看出,即使输入了多个数,也没有输出 循环结束,t1结束这条语句,因为编译器已经认为我比较了那么多次(由于它很快,在输入之前已经比较了无数次),flag就是固定值0,不会变了,以后就不用执行load了。

  • 所以虽然指令重排序可以提高CPU的执行效率,但它也可能会带来一些问题。尤其是在多线程并发执行时,指令重排序可能会导致程序执行结果出现错误,这种问题被称为“内存模型问题”。为了解决这类问题,Java 提供了一些机制,如“volatile”关键字和“synchronized”关键字,用于禁止指令重排序和保证内存可见性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SpringCloud Alibaba】(一)微服务介绍

此专栏内容皆来自于【冰河】的《SpringCloud Alibaba 实战》文档。 1. 专栏介绍 我们先来看看《SpringCloud Alibaba实战》专栏的整体结构吧&#xff0c;先上图 从上图&#xff0c;大家可以看到&#xff0c;专栏从整体上分为十个大的篇章&#xff0c;分别为 专栏设计、微服务…

macOS Ventura 13.5 (22G74) 正式版发布,ISO、IPSW、PKG 下载

macOS Ventura 13.5 (22G74) 正式版发布&#xff0c;ISO、IPSW、PKG 下载 本站下载的 macOS Ventura 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也…

全志F1C200S嵌入式驱动开发(GPIO输出)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 和v3s一样,f1c200s本身的外部引脚比较少。所以这个时候,不可避免地,很多引脚的功能就会重叠在一起。这种情况下,我们就要学会取舍了。比如说,如果是学习sd卡的时候,那么spi的…

阿里云NVIDIA A100 GPU云服务器性能详解及租用费用

阿里云GPU服务器租用费用表包括包年包月、一个小时收费以及学生GPU服务器租用费用&#xff0c;阿里云GPU计算卡包括NVIDIA V100计算卡、T4计算卡、A10计算卡和A100计算卡&#xff0c;GPU云服务器gn6i可享受3折&#xff0c;阿里云百科分享阿里云GPU服务器租用表、GPU一个小时多少…

拦截Bean使用之前各个时机的Spring组件

拦截Bean使用之前各个时机的Spring组件 之前使用过的BeanPostProcessor就是在Bean实例化之后&#xff0c;注入属性值之前的时机。 Spring Bean的生命周期本次演示的是在Bean实例化之前的时机&#xff0c;使用BeanFactoryPostProcessor进行验证&#xff0c;以及在加载Bean之前进…

最新Ai创作源码ChatGPT商用运营源码/支持GPT4.0+支持ai绘画+支持Mind思维导图生成

本系统使用Nestjs和Vue3框架技术&#xff0c;持续集成AI能力到本系统&#xff01; 支持GPT3模型、GPT4模型Midjourney专业绘画&#xff08;全自定义调参&#xff09;、Midjourney以图生图、Dall-E2绘画Mind思维导图生成应用工作台&#xff08;Prompt&#xff09;AI绘画广场自定…

STM32—CAN通信

文章目录 一、CAN通信简介1.1 CAN简介1.2 CAN协议特点1.3 CAN通信的帧类型1.4 数据帧结构1.5 CAN的位时序1.6 CAN的仲裁功能 二、STM32F1的CAN2.1 bxCAN简介2.2 bxCAN工作模式2.2.1 初始化模式2.2.2 正常模式2.2.3 睡眠模式2.2.4 静默模式2.2.5 环回模式 2.3 位时序和波特率 三…

leetCode刷题记录3-面试经典150题

文章目录 不要摆&#xff0c;没事干就刷题&#xff0c;只有好处&#xff0c;没有坏处&#xff0c;实在不行&#xff0c;看看竞赛题面试经典 150 题80. 删除有序数组中的重复项 II189. 轮转数组122. 买卖股票的最佳时机 II 不要摆&#xff0c;没事干就刷题&#xff0c;只有好处&…

阿里云部署 ChatGLM2-6B 与 langchain+ChatGLM

1.ChatGLM2-6B 部署 更新系统 apt-get update 安装git apt-get install git-lfs git init git lfs install 克隆 ChatGLM2-6B 源码 git clone https://github.com/THUDM/ChatGLM2-6B.git 克隆 chatglm2-6b 模型 #进入目录 cd ChatGLM2-6B #创建目录 mkdir model #进入目录 cd m…

安全攻击 --- XSS攻击

XSS跨站脚本攻击 &#xff08;1&#xff09;简介 OWASP TOP 10 之一&#xff0c;XSS被称为跨站脚本攻击&#xff08;Cross-Site-Scripting&#xff09;主要基于JavaScript&#xff08;JS&#xff09;完成攻击行为XSS通过精心构造JS代码注入到网页中&#xff0c;并由浏览器解释…

在nginx上部署nuxt项目

先安装Node.js 我安的18.17.0。 安装完成后&#xff0c;可以使用cmd&#xff0c;winr然cmd进入&#xff0c;测试是否安装成功。安装在哪个盘都可以测试。 测试 输入node -v 和 npm -v&#xff0c;&#xff08;中间有空格&#xff09;出现下图版本提示就是完成了NodeJS的安装…

前端开发实习总结参考范文

▼前端开发实习总结篇四 读了三年的大学&#xff0c;然而大多数人对本专业的认识还是不那么透彻&#xff0c;学的东西真正能够学以致用的东西很少&#xff0c;大家都抱怨没有实践的机会&#xff0c;在很多同学心里面对于本专业还是很茫然。直到即将毕业的时候才知道我们以前学…

【Linux后端服务器开发】HTTPS协议

目录 一、加密算法 二、中间人攻击 三、CA认证 一、加密算法 HTTPS协议是什么&#xff1f;HTTPS协议也是一个应用层协议&#xff0c;是在HTTP协议的基础上引入了一个加密层。 HTTP协议内容是按照文本的方式明文传输的&#xff0c;这就导致在传输过程中出现一些被篡改的情况…

ROS1ROS2之CmakeList.txt和package.xml用法详解

前言&#xff1a;目前还在学习ROS无人机框架中&#xff0c;&#xff0c;&#xff0c; 更多更新文章详见我的个人博客主页【前往】 文章目录 1. CMakeLists.txt与package.xml的作用2. 生成CMakeLists.txt2.1 ROS12.2 ROS2 3. CMakeLists.txt编写3.1 ROS13.2 ROS2 4. package.xml…

Ubuntu 20.04 Ubuntu18.04安装录屏软件Kazam

1.在Ubuntu Software里面输入Kazam&#xff0c;就可以找不到这个软件&#xff0c;直接点击install就可以了 2.使用方法&#xff1a; 选择Screencast&#xff08;录屏&#xff09; Fullscreen&#xff08;全屏&#xff09;-----Windows&#xff08;窗口&#xff09;--------Ar…

1、传统锁回顾(Jvm本地锁,MySQL悲观锁、乐观锁)

目录 1.1 从减库存聊起1.2 环境准备1.3 简单实现减库存1.4 演示超卖现象1.5 jvm锁1.6 三种情况导致Jvm本地锁失效1、多例模式下&#xff0c;Jvm本地锁失效2、Spring的事务导致Jvm本地锁失效3、集群部署导致Jvm本地锁失效 1.7 mysql锁演示1.7.1、一个sql1.7.2、悲观锁1.7.3、乐观…

fragment

fragment 在vue2中,组件必须有一个跟标签在vue3中,组件可以没有跟标签,内部会将多个标签包含在一个fragment虚拟元素中好处:减少标签层级,减小内存占用 teltport 什么是teltport teleport是一种能够将我们组件html结构移动到指定位置的技术 像是下面的代码不适用teleport:…

信息系统项目管理师(第四版)教材精读思维导图-第三章信息系统治理

请参阅我的另一篇文章&#xff0c;综合介绍软考高项&#xff1a; 信息系统项目管理师&#xff08;软考高项&#xff09;备考总结_计算机技术与软件专业技术_铭记北宸的博客-CSDN博客 目录 3.1 IT治理 3.2 IT审计 3.1 IT治理 3.2 IT审计

Java程序设计六大原则设计模式

Java程序设计六大原则 一、单一职责原则&#xff1a; 一个接口或者类只有一个原因引起变化&#xff0c;即一个接口或者类只有一个职责&#xff0c;负责一件事情。&#xff08;此原则同样适用于方法&#xff09; 好处&#xff1a;1、复杂性降低&#xff1b;2、可读性提高&…

elasticsearch查询操作(API方式)

说明&#xff1a;elasticsearch查询操作除了使用DSL语句的方式&#xff08;参考&#xff1a;http://t.csdn.cn/k7IGL&#xff09;&#xff0c;也可以使用API的方式。 准备 使用前需先导入依赖 <!--RestHighLevelClient依赖--><dependency><groupId>org.ela…