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

news2024/11/27 15:41:34

文章目录

  • 前言
  • 线程不安全的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.coloradmin.cn/o/793725.html

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

相关文章

7月《中国数据库行业分析报告》已发布,聚焦图数据库、首发【全球图数据库产业图谱】

为了帮助大家及时了解中国数据库行业发展现状、梳理当前数据库市场环境和产品生态等情况&#xff0c;从2022年4月起&#xff0c;墨天轮社区行业分析研究团队出品将持续每月为大家推出最新《中国数据库行业分析报告》&#xff0c;持续传播数据技术知识、努力促进技术创新与行业生…

Yunfly 一款高效、性能优异的node.js企业级web框架

介绍 Yunfly 一款高性能 Node.js WEB 框架, 使用 Typescript 构建我们的应用。 使用 Koa2 做为 HTTP 底层框架, 使用 routing-controllers 、 typedi 来高效构建我们的 Node 应用。 Yunfly 在 Koa 框架之上提升了一个抽象级别, 但仍然支持 Koa 中间件。在此基础之上, 提供了一…

不同薪资阶段的Android 对“binder 的理解”

转载地址&#xff1a;https://zhuanlan.zhihu.com/p/420660511 面试官提了一个问题&#xff0c;我们来看看三位应聘者的表现如何吧 自认为无所不知&#xff0c;水平已达应用开发天花板&#xff0c;目前月薪 10k 面试官️&#xff1a;谈谈你对 binder 的理解 A&#xff1a;bind…

5-linux中的定时任务调度

定时任务调度 crond 任务调度概述基本语法常用选项快速入门应用实例crond 相关指令 at 定时任务基本介绍at 命令格式at 命令选项at 时间的定义其他指令 crond 任务调度 crontab 进行 定时任务调度 概述 任务调度&#xff1a;是指系统在某个时间执行的特定的命令或程序 任务…

CISP-PTE和CISP-PTS哪个更适合?

CISP-PTE和CISP-PTS是两个与网络安全渗透测试相关的认证&#xff0c;虽然它们有相似之处&#xff0c;但也存在一些区别。 CISP-PTE&#xff1a;证书持有人员主要从事信息安全技术领域网站渗透测试工作&#xff0c;具有规划测试方案、编写项目测试计划、编写测试用例、测试报告…

【C++ 异步任务 】`std::future`

1.基础知识 std::future 是 C 标准库中的一个类模板&#xff0c;定义在 <future> 头文件中。它提供了一种异步操作的机制&#xff0c;用于获取异步任务的结果。 std::future 类模板表示一个未来可能会获得的值。你可以将一个异步任务交给 std::future 来管理&#xff0…

苍穹外卖-day08 java实现 微信支付

苍穹外卖-day08 课程内容 导入地址簿功能代码用户下单订单支付 功能实现&#xff1a;用户下单、订单支付 用户下单效果图&#xff1a; 订单支付效果图&#xff1a; 1. 导入地址簿功能代码 1.1 需求分析和设计 1.1.1 产品原型 地址簿&#xff0c;指的是消费者用户的地址信息&…

Django学习笔记-视图(views)的使用

Django中可以使用views进行管理&#xff0c;类似于WPF的MVVM的ViewModel层&#xff0c;也相当于MVC架构的模Controller层。 一、基于函数的视图FBV&#xff08;Function-Based View&#xff09; 通过定义一个函数&#xff0c;包含HttpRequest对象作为参数&#xff0c;用来接受…

运维高级--shell脚本完成分库分表

为什么要进行分库分表 随着系统的运行&#xff0c;存储的数据量会越来越大&#xff0c;系统的访问的压力也会随之增大&#xff0c;如果一个库中的表数据超过了一定的数量&#xff0c;比如说MySQL中的表数据达到千万级别&#xff0c;就需要考虑进行分库分表&#xff1b; 其…

分享几个电脑录制GIF图片的小工具(附下载链接)

GIF工具 1.GIF1232.ScreentoGif3.gifcam4.LICECAP5.recordit 1.GIF123 点击下载: GIF123 介绍 GIF123&#xff1a;是单文件的绿色软件&#xff0c;无任何外部依赖项&#xff0c;体积仅755KB&#xff0c;支持windows所有版本操作简单&#xff01;&#xff01; 2.ScreentoGif …

梯度提升树的参数

目录 1. 迭代过程 1.1 初始预测结果的设置 1.2 使用回归器完成分类任务 ① 二分类情况 ② 多分类情况 1.3 GBDT的8种损失函数 ① 分类器中的loss a. 二分类交叉熵损失 b. 多分类交叉熵损失 c. 二分类指数损失 d. 多分类指数损失 ② 回归树中的loss a. 平方误差 b…

JPA连接达梦数据库导致auto-ddl失效问题解决

现象&#xff1a; 项目使用了JPA&#xff0c;并且auto-ddl设置的为update&#xff0c;在连接达梦数据库的时候&#xff0c;第一次启动没有问题&#xff0c;但是后面重启就会报错&#xff0c;发现错误为重复建表&#xff0c;也就是说已经建好的表没有检测到&#xff0c;…

spring 的循环依赖以及spring为什么要用三级缓存解决循环依赖

知识铺垫 bean的生命周期 这里简单过一下 class ->无参构造 ->普通对象 ->依赖注入&#xff08;对加了autowire等的属性赋值&#xff09; ->初始化前->初始化 ->初始化后&#xff08;aop&#xff09; ->放入单例池的map&#xff08;一级缓存&#xff09;…

第二次作业 运维高级 MySQL主从复制

1.理解MySQL主从复制原理 1、master&#xff08;binlog dump thread&#xff09;主要负责Master库中有数据更新的时候&#xff0c;会按照binlog格式&#xff0c;将更新的事件类型写入到主库的binlog文件中。 2、I/O thread线程在Slave中创建&#xff0c;该线程用于请求Master&…

有些能力,是工作中学不来的,看看这篇超过90%同行

俗话说:360行&#xff0c;行行转IT。 在就业形势压力巨大的今天&#xff0c;不仅仅是计算机专业的毕业生&#xff0c;很多其他专业的大学生都选择转行从事计算机行业。 尤其是软件测试行业&#xff0c;远远超出其他行业的薪水和广阔的就业前景&#xff0c;吸引了大批应届毕业…

PostgreSQL——Windows上安装PostgreSQL

下载地址&#xff1a;PostgreSQL下载网址 因为某些问题我自己安装的是postgresql-11.2-1-windows-x64 根据下图顺序安装即可&#xff0c;不同版本可能顺序有点区别但每部分目的都是一样的。 首先右键以管理员身份运行&#xff0c;可以弹出安装界面

多通道振动信号经小波变换成多通道信息融合的图像(Python编程)

1.数据集介绍&#xff1a;凯斯西楚大学轴承数据集&#xff08;12KHZ采样频率下&#xff0c;四种不同负载数据集&#xff09; 2.这里以0HP文件夹为例&#xff0c;进行展示&#xff0c;有内圈故障&#xff0c;正常&#xff0c;外圈故障和滚动体故障四个文件夹 内圈故障文件夹下有…

【2023周报】week23 eBPF学习

week23 eBPF学习 如何调试eBPF程序&#xff1f; bpf SystemCall [bpf(2) - Linux manual page (man7.org)](https://man7.org/linux/man-pages/man2/bpf.2.html) [bpf-helpers(7) - Linux manual page (man7.org)](https://man7.org/linux/man-pages/man7/bpf-helpers.7.htm…

Python基本语法之符号使用

好久没有和小伙伴们更新python了&#xff0c;我对于此感到抱歉以后有时间尽量多更新 目录 一. 标识符 A.定义&#xff1a; B.使用特点 C.Python标识符&#xff0c;进一步探讨以下几个方面的详细内容&#xff1a; 1. 规则和约定&#xff1a; 2. 有效的标识符示例&#xff1…

【LeetCode每日一题】——剑指 Offer II 027.回文链表

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【题目进阶】八【题目注意】九【解题思路】十【时间频度】十一【代码实现】十二【提交结果】 一【题目类别】 栈 二【题目难度】 简单 三【题目编号】 剑指 Offer II 02…