(进程线程)的状态和线程安全

进程有两个状态就绪状态和阻塞状态。

这些状态决定了系统会按照什么样的态度来调度这个进程(这些一般是针对一个进程里面有一个线程的情况)。在实际的大多数情况下,一个进程中包含多个线程,其状态则会绑定在线程上。

上诉状态一般是在应用于系统层面上线程的状态(PCB)。我们可以通过Thread里面的方法t.getState()读取到进程t现在的状态。

在java中Thread类中提供了更为细分的状态情况。

NEW:表示创建好了线程,但是还没有调用start方法。

TERMINATED:表示线程执行完了,但是Thread对象还在。

使用join()方法来暂停mian()线程来得到等待t线程执行完了之后的线程状态。

RUNNABLE:就绪状态处于这个状态的线程,在就绪队列中,随时可以被调度到CPU上。(当代码中如果没有sleep,也没有其他可能造成的阻塞的操作,一般线程大概率处在这个状态)

TIMED_WAITING:表示线程处于阻塞状态

线程先在就绪状态等待要执行,然后遇到sleep要休眠处于阻塞状态.处于阻塞时期的线程t时,线程Main已经执行完了。之后在访问线程t状态时就发现因为sleep导致线程t处于阻塞状态。

线程状态转化简图

其中WAITING和BLOCKED还没有涉及到。

线程的安全问题

在多线程中是最重要,最复杂的的问题。操作系统中,调度线程的时候是随机(抢占式执行)

由于这样的执行策略,导致程序很容易出现一些bug。因为这样的调度随机性的引入bug,称代码不安全。反之如果这样的调度没有带了bug,则称之为安全的。

比如设置一个整形变量让两个线程对其进行从零开始的5000自增操作,按逻辑来说应该最后应该变为10000.

最终的输出结果是小于10000,countd的自增内部发生了什么导致误差是八分之一左右这么大。

从计算的底层CPU角度来看,count++实际上是三个CPU的指令分别是

把内存中的count的值,加载到CPU寄存器中.load(加载)
把寄存器中的值,给+1add(增加)
.把寄存器的值写回到内存的count中.save(保存)

在多个线程执行这个操作时由于线程之间会发生抢占式执行,导致线程在同时执行这三个命令时,在执行顺序上会发生随机性。

本来应该先是t1执行完这三个指令,然后t2在执行这三个指令,或者t2先执行,t1后执行。由于随机性导致在t1执行时或者t2执行时,t1执行着t2突然抢占了CPU开始执行导致其运算逻辑发生较大的改变。如下图所示抢占式运行

这样的抢占式执行导致本来应该count自增两次最后结果却自增一次,这个情况就是产生bug 的根源.也就导致了线程不安全问题。

至于上述代码的最终结果是在8749,是因为在极端情况下全发生向上述所说的自增一次的情况下是5000,若在另外一种全部运行正常不发生bug的情况下结果是10000,这两种的概论一般来说都很小所以最后结果一般会处于两者之间。

那如何解决上述问题?

加锁

通过使用加锁限制进程在运行时会一直占用资源不会被其他线程所抢占,保证了其线程的安全性。

我们就可以在如上面的情况之中在自增之前,开始加锁,自增结束之后开始解锁。也就是将多线程的并发执行变为了串行执行,减小了运行的速率增加了线程的安全性。

加锁有多种方式,日常经常使用的是synchronized关键字来进行加锁。在给方法加锁之后,进入方法执行时会给线程自动加锁待执行完成,会自动解锁。并且当一个进程进行加锁成功之后,其他线程也进行加锁会触发阻塞等待线程的状态就是BLOCKED,并且阻塞会一直等待直到另外一个线程开始解锁。

导致线程不安全的原因

线程的抢占式执行,线程之间的调度随机。

多个线程对同一变量进行修改操作

针对变量的操作不是原子的,比如读取变量的值,只是对应一条机器指令,此时这样的操作本身就可以视为是原子的,通过加锁操作将好几个指令给打包成一个原子的了。

内存可见性:有时候针对同一个变量,一个线程进行读操作(循环进行很多次),一个线程进行修改操作(合适的时候执行一次)。假设读操作是t1,修改操作是t2,则t1这个线程会循环读这个变量(读取内存操作,相比于读取寄存器,是一个非常低效的操作!!!(慢3-4个数量级)),因此在t1中频繁的读取这里的内存的值,就会非常低效。而且如果t2线程迟迟不修改, t1线程读到的值又始终是一样的值。因此, t1就有了一个大胆的想法,不再从内存读数据了,而是直接从寄存器里读~~(不执行load 了)。一旦t1做出了这种操作,此时万一t2修改了count值, t1就不能感知到了。这是java编译器优化导致的结果。主流编译器是由各种各样顶尖的人进行实现的,里面会由多种的优化方式,这里面会有各种的优化,当编译器中的代码处于哪种情况时,就会产生这种类似的优化,这种优化会大大提高执行效率不改变内在逻辑。大多数情况都是保证不会出现差错,但在多线程中可能会发生差错。

使用synchronized关键字.
synchronized不光能保证指令的原子性,同时也能保证内存可见性。被synchronized包裹起来的代码,编译器就不敢轻易的做出上述假设,相当于当于手动禁用了编译器的优化。
使用volatile关键字
volatile和原子性无关,但是能够保证内存可见性.
禁止编译器做出上述优化.编译器每次执行判定相等,都会重新从内存读取 isQuit的值!!

按照上述所说线程t不停的读isQuit的值,并且如果mian线程长时间不改的话系统自动会优化使其不更新的读这个值,不会改变。

从输出结果中我们可以看出isQuit的值一改变,t线程里面的循环就被打破,证明了使用volatile是可以使数据一直更新的读取不会被系统默认的进行优化,进行不更新的读操作。

指令重排序

指令重排序,也会影响到线程安全问题
指令重排序,也是编译器优化中的一种操作
日常写的很多代码,在前在后的顺序无所谓不影响程序的正常运行但是在前在后的执行效率是不一样的,所以编译器就会智能的调整这里代码的前后顺序从而提高程序的效率,保证逻辑不变的前提,再去调整顺序
如果代码是单线程的程序,编译器的判定一般都是很准
但是如果代码是多线程的,编译器也可能产生误判。

synchronized关键字

其不止能保证原子性,同时还能保证内存可见性,同时还能禁止指令重排序。

直接修饰普通的方法

​​​​​​​在线程中使用synchronized关键字修饰的方法时会自动对本身进行加锁,因为其会自动调用this方法来指向自己。

修饰代码块

需要显式指定针为哪个对象加锁. (Java 中的任意对象都可以作为锁对象)

修饰一个静态方法

针对当前类的类对象进行加锁,也就是当对其进行使用时会给这个类的所有实例化对象进行加锁。

 


 

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

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

相关文章

计算机408炸了!大多数人都栽在这门课上

组成原理>>数据结构>操作系统>计算机网络 在本科时,我在学习组成原理之前已经学过数字电路和模拟电路,但在接下来学习组成原理时,我依然感到困难。也许是因为自己理解能力不足,总觉得难以掌握,甚至在考研…

算法打卡day28|贪心算法篇02|Leetcode 122.买卖股票的最佳时机 II、55. 跳跃游戏、45.跳跃游戏 II

算法题 Leetcode 122.买卖股票的最佳时机 II 题目链接:122.买卖股票的最佳时机 II 大佬视频讲解:买卖股票的最佳时机 II视频讲解 个人思路 因为只有一只股票,且两天作一个交易单元,那每次只收集正利润就可以最终最多可以获取的利润&#xf…

数据运营常用的8大模型

✅作者简介:《数据运营:数据分析模型撬动新零售实战》作者、《数据实践之美》作者、数据科技公司创始人、多次参加国家级大数据行业标准研讨及制定、高端企培合作讲师。 🌸公众号:风姑娘的数字视角,免费分享数据应用相…

10个优秀的Github开源项目

1Panel 是一个现代化、开源的 Linux 服务器运维管理面板 EX-chatGPT-精准搜索工具 feishu-chatgpt-飞一般的工作体验工具 Knife4j-是一个集Swagger2 和 OpenAPI3为一体的增强解决方案 Kooder 是 Gitee 团队开发的一个代码搜索系统 mtbird 是一款低代码可视化页面生成器 S…

<Linux> 模拟实现文件流 - 简易版

目录 1. FILE 结构设计 2、函数使用及分析 3、文件打开 fopen 4. 缓冲区刷新fflush 5. 数据写入fwrite 6. 文件关闭 fclose 7. 测试 8. 小结 1. FILE 结构设计 在设计 FILE 结构体前,首先要清楚 FILE 中有自己的缓冲区及冲刷方式 缓冲区的大小和刷新方式因…

巧用 20个 Linux 命令贴士与技巧,让你生产力瞬间翻倍?

在本文中,我将向您演示一些专业的Linux命令技巧,这些技巧将使您节省大量时间,在某些情况下还可以避免很多麻烦,而且它也将帮助您提高工作效率。 并不是说这些只是针对初学者的 Linux 技巧。即使有经验的Linux用户也有可能没有发现…

C++ 扫描当前路径下文件并删除大文件

C 扫描当前路径下文件并删除大文件 C获取当前路径扫描文件路径下规定后缀名称的文件计算文件大小 1. 获取当前路径 使用<Windows.h>中的GetCurrentDirectory方法实现&#xff0c;单独编写验证程序如下&#xff1a; #include<iostream> #include<Windows.h&g…

R语言基础入门

1.保存或加载工作空间 改变工作目录——进行文件读写&#xff0c;默认去指定文件进行操作。&#xff08;使用R时&#xff0c;最好先设定工作目录&#xff08;setwd(),getwd()&#xff09;&#xff09; setwd(“工作文件路径”)&#xff1a;建立工作目录 getwd&#xff08;&…

Linux的进程控制(创建和终止)

进程创建 fork 我们前面已经认识过fork函数&#xff0c; 用fork创建新进程后&#xff0c; 新建立的进程为子进程&#xff0c; 该进程为父进程。fork给父进程返回的是子进程的pid&#xff0c; 给子进程返回的是0&#xff0c; 出错时返回-1 进程调用fork后&#xff0c; 当控制…

IS-IS路由

概览&#xff1a; Intermediate System-to-Intermediate System&#xff0c;中间系统到中间系统协议 IS-IS--IGP--链路状态协议--AD值&#xff1a;115 IS--中间系统&#xff08;路由器&#xff09; ES--终端系统&#xff08;PC&#xff09; 在早期IS-IS的开发并不是为了IP…

安防监控视频汇聚平台EasyCVR启用图形验证码之后如何调用login接口?

视频综合管理平台EasyCVR视频监控系统支持多协议接入、兼容多类型设备&#xff0c;平台可以将区域内所有部署的监控设备进行统一接入与集中汇聚管理&#xff0c;实现对监控区域的实时高清视频监控、录像与存储、设备管理、云台控制、语音对讲、级联共享等&#xff0c;在监控中心…

3.25号arm

1. I2C总线 1.1 i2c概述 I2C总线是PHLIPS公司在八十年代初推出的一种串行的半双工总线&#xff0c;主要用于连接整体电路。 I2C总线为两线制&#xff0c;只有两根双向信号线。一根是数据线SDA&#xff0c;另一根是时钟线SCL。 I2C硬件结构简单&#xff0c;接口连接方便&…

【OpenModelica】1 OpenModelica项目架构

1 OpenModelica项目架构 文章目录 1 OpenModelica项目架构一、 架构总览图二、OpenModelica各部分作用 一、 架构总览图 OpenModelica 环境由几个相互连接的子系统组成&#xff0c;如图 1.1 所示。 其中包括&#xff1a; MDT Eclipse 插件图形模型编辑器/浏览器文本模型编辑器…

日本科技巨头富士通遭遇网络攻击,客户数据被窃

日本科技巨头富士通3月15日发布通告&#xff0c;宣称公司经历了一起网络攻击事件&#xff0c;客户个人数据已被黑客窃取。 富士通在一份通知中写道&#xff1a;“我们已经确认有几台商用计算机上存在恶意软件&#xff0c;并且经过我们的内部调查&#xff0c;发现包含个人信息和…

SAP前台处理:物料计价方式:价格控制与价格确定 - 02 <CKM3>

一、背景&#xff1a; 物料主数据中我们讲解到物料的计价方式&#xff0c;SAP应用到的主要计价方式有移动平均价和标准价格方式两种&#xff0c;但也有按照批次计价等方式&#xff0c;我们主要介绍最常用的V2移动平均价和S3的标准价格&#xff1b; 二、示例差异分析&#xff…

k8s入门到实战(二)—— windows安装minikube

minikube 安装 minikube 是一个用于在本地计算机上运行单个节点的 k8s 集群的工具。它允许开发人员可以在自己的计算机上进行本地的 k8s 开发和测试。通过minikube&#xff0c;您可以模拟一个完整的 k8s 集群环境&#xff0c;包括节点、Pod、服务和存储等组件。它是一个轻量级…

Xcode-双架构arm64 x86_64编译

要启用通用构建&#xff0c;在最新版本的 Xcode 中&#xff0c;请打开您的项目设置&#xff0c;然后依次选择&#xff1a; 1. “Build Settings” 选项卡。 2. 在顶部输入框中输入 “Architectures”。 3. 在 “Architectures” 下拉列表中选择 “Other”。 4. 在输入框中输入 …

代码随想录刷题day32|K次反转后最大的数组和加油站分发糖果

文章目录 day34学习内容一、K次反转后最大的数组和1.1、思路1.2、代码-正确写法1.2.1、如何理解if (k % 2 1) &#xff1f;1.2.2、原始nums数组[2,-3,-1,5,-4]&#xff0c;那么排序后数组等于什么&#xff1f; 二、加油站2.1、思路2.2、正确写法12.2.1、 如何理解上面这段代码…

数据可视化-ECharts Html项目实战(7)

在之前的文章中&#xff0c;我们学习了如何设置漏斗图、仪表盘。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢 数据可视化-ECharts Html项目实战&#xff08;6…

JavaScript 学习日记(1)---初识JavaScript

初识JavaScript 文章目录 初识JavaScript一、JavaScript 是什么?二、java 和JavaScript 的关系三、JavaScript 的组成四、JS的基本输入输出 ---> 单行注释五、js变量基本概念六、js基本数据类型七、js转义字符八、js类型转换九、运算符 END! 一、JavaScript 是什么? 我们…
最新文章