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

【多线程初阶】线程状态 线程安全

文章目录

  • 1.线程状态
    • 线程的状态及状态转移
  • 2.多线程带来的风险 - 线程安全(重点)
    • 线程安全问题产生的原因
    • 如何解决线程安全问题

1.线程状态

EE的第一篇总览中有提到过
进程的状态
1.就绪
2.阻塞
在这里插入图片描述

这都是从操作系统的视角看待的
Java线程也是对操作系统线程的封装,针对状态这里,Java也进行了重新封装,来进行表示

线程的状态及状态转移

在这里插入图片描述

  • NEW : 安排了工作,还未开始行动 -->new 了 Thread 对象,还没 start
    在这里插入图片描述

  • TERMINATED : 工作完成了 -->内核中的线程已经结束了,但是 Thread 对象还在
    在这里插入图片描述

  • RUNNABLE : 可工作的,又可以分成正在工作中或即将开始工作的
    就绪: 1).线程正在CPU上执行 2).线程随时可以去CPU上执行
    比如,前面所举的例子,约A开会,A没有出差,随时可以一起开会

在这里插入图片描述

  • TIMED_WAITING : 这几个都表示排队等着其他事情

①.sleep 状态由RUNNABLE 转变为 TIMED_WAITING
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

②.另外,join(时间)也会进入到TIMED_WAITING状态

指定时间的阻塞,线程阻塞(不参与CPU调度,不继续执行了),阻塞的时间是有上限的

在这里插入图片描述

  • WAITING : 这几个都表示排队等着其他事情

与TIMED_WAITING 的区别,WAITING 会死等,没有超时时间的阻塞等待

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

  • BLOCKED : 这几个都表示排队等着其他事情

也是一种阻塞,比较特殊,由于 锁 导致的阻塞

这个线程状态等我们介绍到线程安全问题 涉及到死锁再进行演示

多线程的程序中,理解线程状态,是帮助我们调试程序的关键

比如,发现代码中某个逻辑,好像卡死了(明明调用了,却没有执行/没有执行完)
1.jconsole / 其他工具,查看当前的进程中的所有线程,找到你对应逻辑的线程是谁
2.看线程的状态是啥
看到 TIMED_WAITING / WAITING ,怀疑是不是代码中某个方法产生阻塞,没有被及时唤醒
看到 BLOCKED ,怀疑是不是代码中出现死锁
看到 RUNNABLE ,线程本身没问题,考虑逻辑上某些条件没有预期触发之类的
3.再看看线程具体的调用栈(尤其是 阻塞的状态,线程的代码阻塞在哪一行了…)

2.多线程带来的风险 - 线程安全(重点)

想给出一个线程安全的确切定义是复杂的,但我们可以这样认为:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的

如果不理解线程安全问题,是很难保证写出正确的多线程代码的

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

在这里插入图片描述
再回到我们观察到的现象,多次执行的结果都不一样并且没有达到预期结果

这样的代码很明显就是有BUG
实际执行效果和预期效果不符合,就叫bug
这样的问题,多线程并发执行引起的问题
如果把两个线程,变成串行执行(一个结束,再执行另一个)

我们将代码写成纯串行执行,观察现象
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
CPU内部包含寄存器这样的模块,寄存器也能存一些数据
在这里插入图片描述
我们使用时间轴(先执行的画上面,后执行的画下面)来模拟几个随机调度的顺序,我们只画几种情况,肯定不止这些,因为调度次序存在无数种可能

在这里插入图片描述

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

第一种情况的具体分析

在这里插入图片描述
第三种情况的具体分析

在这里插入图片描述
其他的情况就不一一进行分析了
通过上述讨论,不难发现,如果两个线程load时出现的数据都是0,那么意味着一定会少加一次,如果一个load到0另一个load到1,结果就是正确的,也就是说一个线程load需要再另一个线程的save之后才是正确的,所以我们上述的6种随机调度情况只有前两种是正确的

那么上述代码运行出来一定是 >=5w的吗?是否有可能<5w?
是有可能的,只不过概率很小
可以再用随机调度的情况分析一下,是否存在这种情况
在这里插入图片描述

是有可能的,只不过概率要小很多

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

50次和5000次甚至5w次,线程执行的时间长短是不同的
如果是循环50次,很可能在t2.start开始之前,t1 就算完了等后续t2再执行,虽然代码写的是并行,但变成纯串行了

线程安全问题产生的原因

在这里插入图片描述
本篇文章先讨论前三种原因

  • 1.[根本原因] 操作系统对于线程的调度是随机的,抢占式执行
  • .多个线程同时对同一变量进行修改
    在这里插入图片描述

如果是一个线程修改一个变量 -->没问题
如果是多个线程,不是同时修改同一个变量 -->没问题
如果多个线程修改不同变量 -->没问题
如果多个线程读取同一变量 -->没问题
这些都不会出现中间结果相互覆盖的情况
其中修改操作->写,取值操作->读

  • 3.修改操作,不是原子的

原子性,在数据库-事务的学习中,有提到
事务具有1.原子性 2.一致性 3. 持久性 4.隔离性
其中这个原子性,如果是修改操作.只是对应到一个CPU指令,就可以认为是原子的 ,CPU不会出现"一条指令执行一半"的情况,但是count++ 这行代码对应三条CPU指令,就不是原子的

像是++,–,+=,-=都不是原子的,像 = 赋值操作在Java就是原子的

  • 4.内存可见性问题,引起的线程不安全
  • 5.指令重排序引起的线程不安全

最后两个问题,我们后续再讨论~~

如何解决线程安全问题

  • 1.[根本问题]操作系统对于线程的调度是随机的,抢占式执行

这是操作系统的底层设定,我们左右不了

  • 2.多个线程同时对同一变量进行修改

这个和代码的结构相关,调整代码结构,规避一些线程不安全的代码,但是这样的方案不够通用,有些情况下,需求上就是需要多线程修改同一变量,比如超买/超卖的问题:某个商品,库存100件,能否创建101个订单?

  • 3.修改操作,不是原子的

Java中解决线程安全问题的最主要方案:加锁

计算机中的锁,和生活中的锁,是同样的概念,互斥/排他的
把锁"锁上" 称为 “加锁”
把锁"解开" 称为 “解锁”
一旦把锁加上了,其他人要想加锁,必须要阻塞等待

就可以使用锁,把刚才不是原子操作的 count++ 包裹起来,在count++ 之前,先加锁,然后进行 count++,计算完毕后,在解锁,也就是在执行三条CPU指令过程中,其他线程就没法插队了

加锁操作,不是禁止这个线程被调度走,而是禁止其他线程重新加这个锁,避免其他线程的操作在当前线程执行过程中,插队

加锁/解锁 本身是 操作系统提供的API,很多编程语言都对于这样的API进行了封装,大多数的封装风格,都是采取lock()加锁,unlock()解锁 这两个函数

在Java中使用 synchronized 这样的关键字,搭配代码块,来实现类似的效果

  • 进入 synchronized 修饰的代码块,相当于加锁
  • 退出 synchronized 修饰的代码块,相当于解锁

相关文章:

  • AI书签管理工具开发全记录(六):前端管理基础框框搭建 Vue3+Element Plus
  • 跳动的爱心
  • PowerDesigner通过SQL反向生成类图
  • 【面试】喜茶Java面试题目
  • Axure疑难杂症:中继器图片替换功能优化(支持修改已有记录-玩转中继器)
  • C++ 游戏开发详细流程
  • 当 Python 遇上 Go:Sponge 如何成为替代 Django/Flask 的理想选择
  • JVM——回顾:JVM的起源、特性与系统构成
  • 无人机多旋翼倾转动力测试系统-适用于(eVTOL开发、缩比模型测试、科研教育)
  • 处理知识库文件_编写powershell脚本文件_批量转换其他格式文件到pdf文件---人工智能工作笔记0249
  • PHP 垃圾回收高级特性
  • dockers搭建mysql环境
  • 解决Docker存储空间不足问题
  • Redis 数据恢复的月光宝盒,闪回到任意指定时间
  • powershell 中 invoke-expression 报错解决
  • 机器视觉运动控制一体机在背靠背点胶焊锡机上的应用
  • ESP32-C3 Vscode+ESP-IDF开发环境搭建 保姆级教程
  • Apache SeaTunnel部署技术详解:模式选择、技巧与最佳实践
  • 数学建模之最短路径问题
  • 历年南京大学计算机保研上机真题
  • 衢州市住房建设局 网站/国内打开google网页的方法
  • 网站建设网络推广首选公司/深圳做网站的公司
  • 做企业网站的第一步需要啥/最佳的资源搜索引擎
  • 怎么做网站收录的关键词/代发软文
  • 清远专业网站制作公司/开网店怎么推广运营
  • 二级建造师注册查询系统/新站seo外包