当前位置: 首页 > news >正文

Java开发之高并发必备篇(二)——线程为什么会不安全?

​​上期我们提到了的案例中,三个窗口线程卖票出现了有窗口卖的票是一样的问题,也就是的“线程不安全问题”,这篇文章我们就来聊聊“线程为什么会出现不安全”。

  1. 什么是线程安全?

线程安全最早是由Brian Goetz 在其编写的“Java Concurrency In Practice”(Java并发编程实战)中定义的,它是这样来定义的:

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步,或者在调用方代码不做其他的协调操作,这个对象的行为获取的结果仍然是正确的,那个称这个对象是线程安全的。

在我们之前的三个线程卖票的案例,多个线程访问同一个对象的ticket 数据,但是我们发现操作这个对象获取的数据有的时候出现了重复性的错误结果,所以说我们之前写的卖票案例是“线程不安全”的。

虽然知道了三个线程访问ticket 出现重复数据的现象是线程不安全的,但是不知道为什么多线程并发访问数据不安全,所以接下来我们讲围绕“为什么线程并发访问不安全”来讲解。

  1. 对象的有状态和无状态性

Java中按照状态可以把对象分为有状态和无状态两种;

无状态对象(Stateless Bean):无状态对象就是没有实例变量的对象,所以也无法保存数据,它不包含域也没有引用其他类的域。又因为无状态对象没有存储的数据那么这个对象也没有什么改变之说所以是不可变的,同样的多线程下对该对象的任意操作都不会改变对象的状态。所以“无状态的对象一定是线程安全的”。定义无状态案例如下:

在这里插入图片描述

有状态对象(Stateful Bean):就是有实例变量的对象 ,可以保存数据。

在这里插入图片描述

在这里插入图片描述

我们知道实例的数据是保存在堆中,而堆中的数据是可以被多个线程共享的(如上图);

而在多线程同时访问相同堆中的数据进行读写操作时,就达到了竞态条件,导致多线程在竞争资源读写数据时最后的结果不会像我们预想的那样正确,出现线程不安全的情况。同时修改对象的数据对象的状态也被改变所以被称为有状态对象,定义案例如下:

在这里插入图片描述

  1. 竞态条件

竞态条件是由于当一个对象或者一个不同步的共享状态,被多个线程修改时,会出现由于不恰当的执行时序而出现不正确的结果所引起。分析我们之前的卖票案例:

在这里插入图片描述

如果此时都在访问可共享的对象MyRunnable,如果此时“窗口1”和“窗口2”两个线程同时进入run方法并执行到了图中代码处。由于竞争下执行的代码时间线可能如下:

在这里插入图片描述

由图中可以看出,当“窗口1”线程获取到ticket数据的时候并判断ticket>0 的时候此时ticket为100,而“窗口2”线程此时正在输出ticket结果并且还是100,之后再“窗口1”线程输出100之后,才执行ticket–操作。所以导致两个线程开始输出ticket的值的结果都为100;我们发现在竞态条件下多线程访问的数据是“脏数据”即错误的数据。

  1. 指令重排

其实除了竞态的时候会出现不恰当的执行时序外,指令重排也会导致代码执行的顺序并不是按照你书写顺序的意愿执行的。

代码运行一般步骤是这样的:
1、从主内存中获取指令解码
2、在线程内存中计算值
3、执行代码操作
4、把结果写入主内存(主内存所有线程共享)

而把结果写入主内存的操作比较耗时,CPU为了提高性能,可能不会等它完成,就进行对下一个指令解码计算,这就是指令重排了。定义如下:

指令重排:

计算机为了性能优化会对汇编指令进行重新排序,以便充分利用硬件的处理性能。

经典案例:

在这里插入图片描述

分析:虽然我们代码书写的顺序是“步骤1”、“步骤2”、“步骤3”但是CPU在处理的时候会根据性能最优执行代码并保证最终结果不变。也就是说最后的步骤都是“步骤3”进行“a+b”运算,但是“步骤1”和“步骤2”执行的顺序可能为先三种:

(1)“步骤1”先于“步骤2”

(2)“步骤1”和“步骤2”同时间片内执行

(3)“步骤1”后于“步骤2”执行

指令重排会虽然会改变代码执行的顺序,但是在单线程下最后执行的结果是不变的,所以在单线程下是没有什么问题的;而如果在多线程中,同时操作一个数据,如果一个读,一个写,当写的线程值已经改变了但是还没写入主内存时(也就是说值的改变其他线程还没有看到),另一个线程已经开始读取了,那么这个时候就会出现和预期不一致的结果。

其实现在对于多线程并发为什么会出现不安全的问题已经很清楚了,究其根本是因为多线程是不共享的,并且也无法准确的知道互相之间的状态,包括值的修改也无法可见才会导致修改数据出现问题,出现线程不安全的问题。

上面介绍了一个所有线程共享的主内存,那么主内存又是什么呢?线程运行的内存模型是怎么的呢?别急,下篇内容我们就介绍下线程模型和java线程的内存模型。

相关文章:

  • 低代码技术研究路径解读|低代码的产生不是偶然,是数字技术发展的必然
  • OPT华东产业园封顶,机器视觉产业版图再扩大!
  • 多肽RGD修饰乳清白蛋白/肌白蛋白/豆清白蛋白/蓖麻蛋白/豌豆白蛋白1b ( PA1b)纳米粒(实验原理)
  • 基于Mybatis-Plus扩展批量插入或更新InsertOrUpdateBath
  • LeetCode·701.二叉搜索树中的插入操作·递归
  • 数据结构试题(一)
  • DevSecOps 安全即代码基础指南
  • js字符串对比之localeCompare()方法-对字符串进行排序——大于0-升序、小于0-降序 对el-table的列进行排序sort-change
  • Vue开发环境安装
  • springboot小型命题系统毕业设计源码011508
  • 61-70==c++知识点
  • 一文快速上手 Nacos 注册中心+配置中心!
  • 云扩RPA携手中联教育引领财务机器人教学创新
  • 入阿里P6?最少啃完这本阿里最新Java多线程编程手册,建议收藏
  • 【毕业设计】深度学习人脸表情识别系统 - python OpenCV
  • #Java异常处理
  • HTML中设置input等文本框为不可操作
  • JavaScript 一些 DOM 的知识点
  • Javascript基础之Array数组API
  • Java知识点总结(JavaIO-打印流)
  • JS实现简单的MVC模式开发小游戏
  • WePY 在小程序性能调优上做出的探究
  • yii2中session跨域名的问题
  • 闭包--闭包作用之保存(一)
  •  一套莫尔斯电报听写、翻译系统
  • 一天一个设计模式之JS实现——适配器模式
  • RDS-Mysql 物理备份恢复到本地数据库上
  • ​configparser --- 配置文件解析器​
  • #Spring-boot高级
  • #我与Java虚拟机的故事#连载12:一本书带我深入Java领域
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (pojstep1.1.2)2654(直叙式模拟)
  • (超详细)语音信号处理之特征提取
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (一)Dubbo快速入门、介绍、使用
  • (转)【Hibernate总结系列】使用举例
  • (转)jQuery 基础
  • (转)mysql使用Navicat 导出和导入数据库
  • (转)真正的中国天气api接口xml,json(求加精) ...
  • ./configure,make,make install的作用(转)
  • .NET Core 成都线下面基会拉开序幕
  • .NET LINQ 通常分 Syntax Query 和Syntax Method
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • .NET 使用 XPath 来读写 XML 文件
  • .net 写了一个支持重试、熔断和超时策略的 HttpClient 实例池
  • .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)
  • .NET/C# 使用 #if 和 Conditional 特性来按条件编译代码的不同原理和适用场景
  • .so文件(linux系统)
  • [ CTF ]【天格】战队WriteUp- 2022年第三届“网鼎杯”网络安全大赛(青龙组)
  • [20171106]配置客户端连接注意.txt
  • [ajaxupload] - 上传文件同时附件参数值
  • [Angular] 笔记 7:模块
  • [BT]BUUCTF刷题第4天(3.22)
  • [Flexbox] Using order to rearrange flexbox children