线程安全问题(面试重难点)

这里只是简单介绍以下线程安全,具体情况要结合代码进行判断

线程 是随机调度,及 抢占式执行 ,具有随机性,就可能会让我们的结果出现不同

当我们得到的结果并不是我们想要的时候(不符合需求),就会被认定为BUG,此时就是出现了线程安全问题

那么存在线程不安全的代码就被认为是 "线程不安全"

概念

我们先来看一个非常典型的线程不安全的例子

因为线程之间是并发执行的,所以我们为线程添加上等待,也就是说,理论上我们的代码运行的结果应该为 10000

public static int count = 0;
public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});t.start();t2.start();t.join();//如果没有join,三个线程同时执行,则最后输出并不是t t2 计算完的效果(并发执行)t2.join();System.out.println("count = "+count);//预期是10000,但是实际值小于预测值,且每次不一样--->BUG-->少加
}

实际结果: 与我们预期不相符,那么此时就代表出现了 线程安全问题

那么为什么会出现这样的结果呢?原因就在于多线程的并发

CPU 执行指令,实际上就是执行一个线程的过程,加上多线程就会变得更加复杂

例如,代码中的 count++,虽然表面上是一行代码,但实际上是三个 CPU 指令

  1. 读取内存中的count数值到 CPU 寄存器中

  2. 寄存器中的值+1,但是此时修改过的值仍然在寄存器中,内存中的count没有发生变化

  3. 将寄存器中计算后的值,写回内存中的count

就像你完成作业,虽然听起来只是一个动作,但是也要你将书包里的作业本拿出来,将作业写好,再将作业放回书包里面,这三步操作

所以当两个线程同时都要count++时,这个顺序可能就会发生改变,就会出现以下情况

比如:

线程1将内存中的count拿出到寄存器,还没有完成放回操作

此时线程2也去内存中拿出count,那么此时,因为线程1还没有放回,就代表内存中的count没有被修改

也就是说,在这种条件下,线程2拿到的count和线程1拿到的是数值一样的,那么放回的也就会是一样的,此时线程1和线程2的语句也都执行了,count也分别被线程1和线程2改变了

只不过线程1和线程2的改动是一样的

只要我们将线程运行的顺序稍作修改:

t.start();
t.join();
t2.start();
t2.join();

此时得到的结果就是:

也就是说

如果多线程环境下代码运行的结果不符合预期(单线程下的结果),那么这个线程就是不安全的,反之,线程安全

原因

我们要先明确,多线程的运行逻辑: 线程是抢占式运行

修改共享数据

就是上面代码的那一种情况,涉及到多个线程对同一变量进行更改

这个变量就是多个线程都能访问到的共享数据

所以对于修改数据只有三种安全的情况:

  1. 单线程修改不同变量

  2. 多线程读取同一变量(这里并不涉及修改,只能获取)

  3. 多线程修改不同变量

原子性

什么叫做原子性呢?

指在多线程环境下,某一个操作要么完全执行,要么完全不执行,不会出现执行到一半被打断的情况,这个操作是不可分割的

举个例子:

将一段代码比作一个公共文档,在没有任何设置的情况下,任何人都可以对公共文档进行修改,而多线程中的线程就是要修改文档的人.当A正在进行修改的时候,B也要进行修改的话,可能就会因为多端同时修改而导致文档无法保存

更通俗一点讲就是:

如果我们没有考虑原子性:

购买火车票时,可能就会因为同时点击同一张票,并且同时进行付款,那么这张票很可能就会被卖两次

那么此时我们就要对文档进行设置,即一次只能允许一个人进行修改,当线程A进行修改时,其他线程就不能进行修改了,这样就保证了代码的原子性了

不过要注意的是, 一条 Java 语句不一定是原子的,也不一定只是一条指令 

比如 count++ ,就是一个典型的例子,他是一行语句,但是在CPU角度是三条指令

如果一个线程正在对一个变量进行操作,中途有其他线程插入进来了,如果操作被打断了,可能就会造成结果错误

也就是说,只要操作是原子的,那么多线程同时进行操作也就不会出现安全问题

比如: 直接对 int/double 直接赋值 --> 多个线程同时进行赋值也是不会出现问题的

可见性

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

在这之前,我们要先了解JMM(Java内存)

用来屏蔽掉各种硬件和操作系统的内存访问差异,来实现让Java程序在各种平台都能达到一致的并发效果

每一个线程都是有自己的工作内存的,相当于同一个共享变量的 "副本",修改线程A的工作内存的值,线程B的工作内存的值不一定会变化

就像上面的例子,count++

线程A已经修改了,但是线程B获取的count还是修改之前的主内存的count

代码顺序性

代码在编写时,也是需要对顺序进行考虑的

比如,有一段代码是这样的:

  1. 对变量A+5

  2. 对变量B-3

  3. 对变量A+3

  4. 最后输出变量A和B

在单线程的情况下,JVM、CPU指令集会代码进行优化,比如按照 1->3->2->4 的顺序进行执行,也就是直接对A+8,减少一次加法,这就是指令重排序

但是并不是所有的代码都可以进行重排序的!! 只有逻辑不发生变化的情况下,才能进行重排序

问题解决

这里暂时只给出代码来解决上述的问题

这里使用的方法是 --> 加锁 ,也就是将非原子代码转换为原子代码

public static int count = 0;
public static void main(String[] args) throws InterruptedException {//先创建一个对象,使用这个对象来作为锁Object locker = new Object();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (locker){//同步,这里指的是互斥count++;}}});//锁先被t1占领,t2先进入阻塞等待,只有当t1进行完毕之后,t2才能占领锁//这样t2的load就在t1的save之后  但是两个线程都是并发执行的Thread t2 = new Thread(()->{for (int i = 0; i <50000 ; i++) {synchronized (locker){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = "+count);
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构第七节:红黑树(初阶)

【本节要点】 红黑树概念红黑树性质红黑树结点定义红黑树结构红黑树插入操作的分析 一、红黑树的概念与性质 1.1 红黑树的概念 红黑树 &#xff0c;是一种 二叉搜索树 &#xff0c;但 在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是 Red和 Black 。 通过对 任何…

读书报告」网络安全防御实战--蓝军武器库

一眨眼&#xff0c;20天过去了&#xff0c;刷完了这本书「网络安全防御实战--蓝军武器库」&#xff0c;回味无穷&#xff0c;整理概览如下&#xff0c;可共同交流读书心得。在阅读本书的过程中&#xff0c;我深刻感受到网络安全防御是一个综合性、复杂性极高的领域。蓝军需要掌…

从传统到智能:Node-red工控机助力农业大棚高效监控

智慧农业逐渐成为现代农业发展的主流方向。在这一背景下&#xff0c;农业用工控机&#xff08;简称“农控机”&#xff09;作为智慧农业的核心设备之一&#xff0c;正在为农业大棚的智能化管理提供强有力的支持。本文将详细探讨农控机在智慧农业大棚监控中的应用&#xff0c;并…

硬件学习笔记--48 磁保持继电器相关基础知识介绍

目录 1.磁保持继电器工作原理 2.磁保持继电器内部结构及组成部分 3.磁保持继电器主要参数 4.总结 1.磁保持继电器工作原理 磁保持继电器利用永磁体的磁场和线圈通电产生的磁场相互作用&#xff0c;实现触点的切换。其特点在于线圈断电后&#xff0c;触点状态仍能保持&#…

WOA-Transformer鲸鱼算法优化编码器时间序列预测(Matlab实现)

WOA-Transformer鲸鱼算法优化编码器时间序列预测&#xff08;Matlab实现&#xff09; 目录 WOA-Transformer鲸鱼算法优化编码器时间序列预测&#xff08;Matlab实现&#xff09;预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现WOA-Transformer鲸鱼算法优化编…

K8S学习之基础十九:k8s的四层代理Service

K8S四层代理Service 四层负载均衡Service 在k8s中&#xff0c;访问pod可以通过ip端口的方式&#xff0c;但是pod是由生命 周期的&#xff0c;pod在重启的时候ip地址往往会发生变化&#xff0c;访问pod就需要新的ip地址&#xff0c;这样就会很麻烦&#xff0c;每次pod地址改变就…

R语言的基础命令及实例操作

> T & F [1] FALSE > T & T [1] TRUE > T | F [1] TRUE > F | F [1] FALSE > a <- c(T,F,T) > b <- c(F,F,T) > a & b [1] FALSE FALSE TRUE > a | b [1] TRUE FALSE TRUE 在 R 中&#xff0c;大小写是敏感的&#xff0c;也就是说…

LLM 模型 Prompt 工程

目录 1、Prompt 基础概念 2、Prompt 主要构成 3、Prompt 相关技术 3.1、思维链 3.2、自洽性 3.3、思维树 1、Prompt 基础概念 Prompt 工程是通过设计和优化自然语言提示&#xff08;Prompt&#xff09;&#xff0c;引导LLM生成符合特定任务需求的输出的技术。其核心目标是…

Springboot基础篇(4):自动配置原理

1 自动配置原理剖析 1.1 加载配置类的源码追溯 自动配置的触发入口&#xff1a; SpringBootApplication 组合注解是自动配置的起点&#xff0c;其核心包含 EnableAutoConfiguration&#xff0c;该注解使用AutoConfigurationImportSelector 实现配置类的动态加载。 启动类的注…

【大模型系列】开发工具Cursor使用配置及备忘

开发工具cursor使用过程的配置备忘 最近一段时间大模型开发工具cursor是比较火爆的&#xff0c;其提供的一个比较有价值的特性就是其ai辅助功能&#xff0c;其内部集成了若干大模型 提供免费使用期&#xff1b; 做大模型开发这个话题应该是绕不过的&#xff0c;就像开发java使…

vtkAppendPolyData vtkMultiBlockDataGroupFilter 区别 合并数据

Summary: vtkAppendPolyData vtkMultiBlockDataGroupFilter 区别 两个都是合并数据&#xff1b; 用于处理多块数据集的两种不同的过滤器&#xff08;filters&#xff09;&#xff0c;它们在处理和合并多块数据集方面有不同的用途和实现方式。 Part2:区别 它们的主要区别在于…

C++入门——输入输出、缺省参数

C入门——输入输出、缺省参数 一、C标准库——命名空间 std C标准库std是一个命名空间&#xff0c;全称为"standard"&#xff0c;其中包括标准模板库&#xff08;STL&#xff09;&#xff0c;输入输出系统&#xff0c;文件系统库&#xff0c;智能指针与内存管理&am…

定制开发开源AI智能名片S2B2C商城小程序:以“晒”为桥,构建信任,助力社交新零售飞跃

摘要&#xff1a;随着互联网的深入发展和社交媒体的普及&#xff0c;社交新零售逐渐成为商业领域的新热点。在这个充满机遇与挑战的时代&#xff0c;如何快速建立与陌生消费者的信任关系&#xff0c;成为决定商业成功的关键。本文将以定制开发开源AI智能名片S2B2C商城小程序为研…

【Linux】Linux Progress Pulse-进度条

> &#x1f343; 本系列为Linux的内容&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; > &#x1f38a;个人主页:【小编的个人主页】 >小编将在这里分享学习Linux的心路历程✨和知识分享&#x1f50d; >如果本篇文章有问题&#xff0c;还请多多包涵&a…

Zypher Network :基于零知识证明方案为 AI 赋予可信框架

Zypher Network 提出的系列方案正逐步成为破解这一困局的关键&#xff0c;其不仅为 LLM 和 AI Agent 等采用提供了一个可信的框架&#xff0c;也为其在更广泛行业中的应用铺平了道路。 LLM 的 “黑盒特性” 像 ChatGPT、DeepSeek、Grok 等大型语言模型&#xff08;LLM, Large …

从Manus到OpenManus:多智能体协作框架如何重构AI生产力?

文章目录 Manus&#xff1a;封闭生态下的通用AI智能体OpenManus&#xff1a;开源社区的闪速复刻挑战与未来&#xff1a;框架落地的现实边界当前局限性未来演进方向 OpenManus使用指南1. 环境配置2. 参数配置3. 替换搜索引擎4. 运行效果 协作框架开启AI生产力革命 Manus&#xf…

深入理解与配置 Nginx TCP 日志输出

一、背景介绍 在现代网络架构中&#xff0c;Nginx 作为一款高性能的 Web 服务器和反向代理服务器&#xff0c;广泛应用于各种场景。除了对 HTTP/HTTPS 协议的出色支持&#xff0c;Nginx 从 1.9.0 版本开始引入了对 TCP 和 UDP 协议的代理功能&#xff0c;这使得它在处理数据库…

Python - 轻量级后端框架 Flask

Flask是什么&#xff1f; Flask是一个轻量级的Python Web框架&#xff0c;用于构建Web应用程序和API。简单、灵活、易扩展&#xff0c;适合小型项目或需要快速开发的应用。 接口的输入和输出 输入&#xff1a;request GET参数、POST JSON数据、POST表单 from flask import…

<论文>MiniCPM:利用可扩展训练策略揭示小型语言模型的潜力

一、摘要 本文跟大家一起阅读的是清华大学的论文《MiniCPM: Unveiling the Potential of Small Language Models with Scalable Training Strategies》 摘要&#xff1a; 对具有高达万亿参数的大型语言模型&#xff08;LLMs&#xff09;的兴趣日益增长&#xff0c;但同时也引发…

好玩的谷歌浏览器插件-自定义谷歌浏览器光标皮肤插件-Chrome 的自定义光标

周末没有啥事 看到了一个非常有意思的插件 就是 在使用谷歌浏览器的时候&#xff0c;可以把鼠标的默认样式换一个皮肤。就像下面的这种样子。 实际谷歌浏览器插件开发对于有前端编程基础的小伙伴 还是比较容易的&#xff0c;实际也是写 html css js 。 所以这个插件使用的技术…