【JavaEE初阶】多线程(3)

news/2024/9/17 1:20:17/ 标签: java, 开发语言

欢迎关注个人主页:逸狼


创造不易,可以点点赞吗~

如有错误,欢迎指出~



目录

线程状态

线程安全

代码示例 

解释

总结原因

解决方案-->加锁

t1和t2都加锁 且 同一个锁对象

t1和t2中只有一个加锁了

t1和t2都加锁,但锁对象不同

加锁 与线程等待(join) 的区别

使用类对象加锁

加锁场景

死锁

场景1

场景2

​编辑场景3

构成死锁的4个必要条件(缺一不可)

解决方案举例

方案1 避免锁嵌套

方案2 约定加锁顺序

方案3 银行家算法


线程状态

进程状态 分为两种:

  • 就绪:正在cpu上执行,或者随时可以去cpu上执行
  • 阻塞:暂时不能参与cpu执行

Java的线程状态有 6 种

  1. NEW  当前Thread对象虽然有了,但是内核的线程还没有(还没调用start)
  2. TERMINATED 当前Thread对象虽然还在,但是内核线程已经销毁(线程已经结束)
  3. RUNNABLE  就绪状态,正在cpu上运行 +  随时可以去cup上运行
  4. BLOCKED  因为锁竞争引起的阻塞
  5. TIMED_WAITING  有超时的等待 如sleep,或者join带参数版本
  6. WAITING  没有超时 时间的阻塞等待 如 join/wait

学习线程状态主要为了 调试,比如 遇到某个代码功能没有执行,就可以观察对应线程的状态,看是否是因为一些原因阻塞了.

线程安全

多个线程同时执行某个代码时,可能会引起一些奇怪的bug,理解线程安全才能 避免/解决 上述bug

代码示例 

public class Demo12 {public static int count=0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("count="+ count);}
}

因为多线程并发执行 引发的bug称为"线程安全问题" 或"线程不安全"

解释

上述代码count++;在cpu视角是3个指令:

  1. load   把内存中的数据读到cpu寄存器里
  2. add    把cpu寄存器里的数据+1
  3. sava  把寄存器的值,写回内存

指令是cpu执行的基本单位,如果要调度,cup至少会把当前指令执行完,

cpu调度执行线程的方式是 抢占式执行,随机调度,

但是由于count++ 是三个指令,可能会出现cpu执行了其中1个指令或2个指令或3个指令就被调度走的情况(都有可能,无法预测), 所以 两个线程同时对count进行++ 就容易出现bug

由于循环5w次过程中不知道有多少次的执行顺序是前两种正确情况,有多少次是其他错误情况,最终的结果就是一个不确定的值,而这个值 一定小于10w

但结果也有可能小于5w

总结原因

  • 线程在操作系统中,随机调度,抢占式执行(根本原因), 此原因无法干预(操作系统内核,作为应用层的程序员无法干预)
  • 多个线程,同时修改一个变量(如果是一个线程修改,就没事)
  • 修改操作,不是"原子"的,(对cpu来说,一条指令才是"原子"的,是不可分割的最小的单位)

解决方案-->加锁

解决线程安全问题,最主要的方法就是 把"非原子" 的修改,变成"原子"(通过加锁,把非原子的修改操作 打包成一个整体,变成原子操作)

t1和t2都加锁 且 同一个锁对象

此处的加锁,没有干预到线程的调度,只是通过加锁,使一个线程在执行count++时,其他线程的count++不能插队进来

Java提供synchronized关键字 来完成加锁操作,synchronizede()的'()'中需要指定一个 "锁对象" (可以指定任何对象)来进行后面的判定

t1和t2都是针对locker对象加锁,t1加锁成功后,继续执行{}里的代码,t2后加锁,发现locker对象已经被别人先锁了,t2只能排队等待(这两者的++ 操作不会并发执行了,本质上是把随机并发的执行过程 强制变成了串行,从而解决了刚才的线程安全问题)

t1和t2中只有一个加锁了

t1和t2都加锁,但锁对象不同

锁对象的作用 就是用来区分 两个线程或多个线程 是否针对"同一个对象"加锁

  • 若是,此时就会出现阻塞(锁竞争/锁冲突)
  • 若不是,此时不会出现"阻塞",两个线程仍然是 随机调度的并发执行.

锁对象,只要是Object(或者其子类)都行,不能是int,double这样的内置类型

加锁 与线程等待(join) 的区别

上述加锁后的代码 本质上要比join的串行执行 的效率还要高

  • 加锁只是把线程中一小部分逻辑 变成了 串行执行,剩下其他部分仍然可以并发执行
  • join是 线程 整体都串行执行

使用类对象加锁

一个Java进程中,一个类的类对象是只有唯一一个的,类对象,也是对象,也可以成为锁对象,写类对象和写其他对象 没有本质区别,换句话说,写成类对象,就是'偷懒'的做法(不想单独创建锁对象了~)

 

加锁场景

是否要加锁,怎么加锁,都是和具体场景直接相关的("无脑加锁"是不推荐的)

锁 ,需要的时候才使用,不需要的时候不要使用,否则会付出代价(性能)

使用锁,就可能会发生阻塞,一旦某个线程阻塞,啥时候恢复阻塞 继续执行是不可预期的~

死锁

场景1

一个线程,针对一把锁,连续加锁两次

Java的synchronized做了特殊处理(引入了特殊机制,"可重入锁"),不会出现 死锁,但同样的代码换成c++/python就会死锁

可重入锁就是在锁中 额外记录一下 当前是哪个线程,对哪个锁加锁了,后续加锁时就会进行判定

还会引入一个引用计数,维护当前已经加锁几次了,并且描述何时真正释放锁

场景2

两个线程,两把锁

  1. 线程1先针对A加锁,线程2针对B加锁
  2. 线程1不释放锁A的情况下,在针对B加锁,同时线程2不释放锁B的情况下对A加锁

这种情况,可重入锁 也无能为力~

package thread;public class Demo17 {private static Object locker1 = new Object();private static Object locker2 = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (locker1) {System.out.println("t1 加锁 locker1 完成");// 这里的 sleep 是为了确保, t1 和 t2 都先分别拿到 locker1 和 locker2 然后在分别拿对方的锁.// 如果没有 sleep 执行顺序就不可控, 可能出现某个线程一口气拿到两把锁, 另一个线程还没执行呢, 无法构造出死锁.try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2) {System.out.println("t1 加锁 locker2 完成");}}});Thread t2 = new Thread(() -> {synchronized (locker2) {System.out.println("t2 加锁 locker2 完成");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1) {System.out.println("t2 加锁 locker1 完成");}}});t1.start();t2.start();}
}

场景3

N个线程,M个锁 

经典问题:哲学家就餐问题

当每根筷子都被哲学家左手拿起来了,他们右手就没有筷子可以拿了,当哲学家吃不到面条时,也就不会放下左手的筷子, 此时就产生了 死锁.

构成死锁的4个必要条件(缺一不可)

锁的基本特性:

  • 1.锁是互斥的 . 如 一个线程拿到锁,另一个线程就拿不到这个锁
  • 2.锁是不可被抢占的.  如 线程1拿到了锁A,若线程1不主动释放A,线程2不能把锁A抢过来

对于synchronized 这样的锁,互斥和不可抢占都是基本特性, 无法干预

代码结构上:

  • 3.请求 和 保持. 如 线程1拿到锁A之后,不释放A的前提下,去拿锁(解决方法: 如果是 先释放A,再拿B,不会有问题)
  • 4.循环等待 / 环路等待 / 循环依赖. 如 多个线程获取锁时,存在 循环等待( 解决方法:如果在获取多把锁的时候,不要构成循环等待就行了)

解决方案举例

针对场景2,通过改变代码结构 解决死锁问题(对症下药~)

方案1 避免锁嵌套

针对 死锁构成条件3 

方案2 约定加锁顺序

针对 死锁构成条件4 

给锁进行编号1,2,3,4...N,约定所有的线程在加锁时都必须按照一定的顺序加锁(比如,必须先针对编号小的锁,加锁,后对大的加锁)

方案3 银行家算法

但是银行家算法太复杂了,如果在日常开放中,实现一套银行家算法解决死锁,先不说死锁的问题是否存在,你实现的银行家算法本身可能存在bug~


http://www.ppmy.cn/news/1522826.html

相关文章

35天学习小结

距离上次纪念日&#xff0c;已经过去了35天咯 算算也有5周了&#xff0c;在这一个月里&#xff0c;收获的也挺多&#xff0c;在这个过程中认识的大佬也是越来越多了hh 学到的东西&#xff0c;其实也没有很多&#xff0c;这个暑假多多少少还是有遗憾的~ 第一周 学习了一些有…

图像处理与编辑软件Adobe Photoshop(PS)2024WIN/MAC下载及安装教程

目录 一、软件概述 1.1 Photoshop 简介 1.2 主要功能 二、下载与安装 2.1 下载 2.2 安装步骤 2.3 注意事项 三、系统要求 3.1 硬件要求 3.2 操作系统 四、操作指南 4.1 基础操作 4.2 进阶技巧 4.3 高效工作 一、软件概述 1.1 Photoshop 简介 Adobe Photoshop&a…

Android13 Hotseat客制化--去掉hotseat(热座)

需求&#xff1a;有些项目不要热座&#xff0c;要求去掉热座 以前的做法是把DeviceProfile里与hotseat有关的变量改为0之类的&#xff0c;改动比较大。为什么不通过简单的把mHotseat设置为GONE呢 因为在各种视图变化的时候&#xff0c;会把hotseat再次显示出来&#xff0c;因…

Spark的一些高级用法

Java 中实现 Spark 的一些高级用法。 1. 使用 DataFrame 和 Spark SQL 在 Spark 中&#xff0c;使用 DataFrame 来处理结构化数据并执行 SQL 查询是非常常见的。 import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.Spark…

2024高教社杯数学建模国赛ABCDE题选题建议+初步分析

提示&#xff1a;DS C君认为的难度&#xff1a;C<B<A&#xff0c;开放度&#xff1a;A<C<B 。 D、E题推荐选E题&#xff0c;后续会直接更新E论文和思路&#xff0c;不在这里进行选题分析&#xff0c;以下为A、B、C题选题建议及初步分析 A题&#xff1a;“板凳龙”…

RPC使用的关键技术

RPC&#xff08;Remote Procedure Call&#xff0c;远程过程调用&#xff09;是分布式系统中常用的一种通信方式&#xff0c;它允许程序调用位于不同计算机上的方法或函数&#xff0c;就像调用本地方法一样。为了实现这种透明且高效的远程调用&#xff0c;RPC 框架依赖于多种关…

【软件设计】常用设计模式--策略模式

软件设计模式&#xff08;三&#xff09; 策略模式&#xff08;Strategy Pattern&#xff09;1. 概念2. 模式结构3. UML 类图4. 实现方式C# 示例步骤1&#xff1a;定义策略接口步骤2&#xff1a;实现具体策略类步骤3&#xff1a;实现上下文类步骤4&#xff1a;使用策略模式 Jav…

驱动(RK3588S)第八课时:平台设备总线

目录 目标一、平台设备总线的概念1、什么是平台设备总线2、平台设备总线 platform 的匹配3、设备树和平台设备总线的关系&#xff0c;以及匹配 二、平台设备总线的函数接口1、注册设备端的资源信息2、设备端提供的资源的信息3、注销申请的设备端的资源4、驱动端的函数&#xff…

逻辑表达式,最小项

目录 得到此图的逻辑电路 1.画出它的真值表 2.根据真值表写出逻辑式 3.画逻辑图 逻辑函数的表示 逻辑表达式 最小项 定义 基本性质 最小项编号 最小项表达式 得到此图的逻辑电路 1.画出它的真值表 这是同或的逻辑式。 2.根据真值表写出逻辑式 3.画逻辑图 有两种画法…

Android Fragment 学习备忘

1.fragment的动态添加与管理&#xff0c;fragment生命周期在后面小节&#xff1a;https://www.bilibili.com/video/BV1Ng411K7YP/?p37&share_sourcecopy_web&vd_source982a7a7c05972157e8972c41b546f9e4https://www.bilibili.com/video/BV1Ng411K7YP/?p37&share_…

Python 读取 Excel 数据|数据处理|Pandas|Excel操作

目录 1. 为什么选择 Python 读取 Excel 数据 2. Python 读取 Excel 数据的基本工具 2.1 Pandas 库 2.2 Openpyxl 库 2.3 xlrd 库 3. 读取 Excel 文件的高级操作 3.1 读取特定的工作表 3.2 读取特定的列和行 3.3 处理缺失数据 4. 实践应用示例 4.1 数据分析和可视化 …

ngrok | 内网穿透,支持 HTTPS、国内访问、静态域名

前言 当我们需要把本地开发的应用展示给外部用户时&#xff0c;常常会因为无法直接访问而陷入困境。 就为了展示一下&#xff0c;买服务、域名&#xff0c;搭环境&#xff0c;费钱又费事。 那有没有办法&#xff0c;让客户直接访问自己本机开发的应用呢&#xff1f; 这种需…

表格多列情况下,loading不显示问题

问题描述&#xff1a; 用element plus 做得表格&#xff0c;如下图&#xff0c;列数较多&#xff0c;且部分表格内容显示比较复杂&#xff0c;数据量中等的情况下&#xff0c;有一个switch 按钮&#xff0c;切换部分列的显示和隐藏&#xff0c;会发现&#xff0c;切换为显示的时…

逻辑运算基础知识

关系运算符 <:小于 <:小于等于 >:大于 >:大于等于 以上优先级相同&#xff1a;高 &#xff1a;等于 !&#xff1a;不等于 以上优先级相同&#xff1a;低 说明&#xff1a; 关系运算符的 优先级 低于 算数运算符 关系运算符的 优先级 大于 赋值运算符 逻辑运算&a…

前向渲染路径

1、前向渲染路径处理光照的方式 前向渲染路径中会将光源分为以下3种处理方式&#xff1a; 逐像素处理&#xff08;需要高等质量处理的光&#xff09;逐顶点处理&#xff08;需要中等质量处理的光&#xff09;球谐函数&#xff08;SH&#xff09;处理&#xff08;需要低等质量…

如何使用 PHP 函数与其他 Web 服务交互?

在 PHP 中&#xff0c;我们可以使用 cURL 或者 file_get_contents 函数与其他 Web 服务进行交互。 使用 cURL 函数 cURL 是一个库&#xff0c;它允许你使用各种类型的协议来发送数据&#xff0c;并从服务器获取数据。 $curl curl_init(‘http://example.com/api’); curl_s…

SprinBoot+Vue漫画天堂网的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质…

前端框架有哪些?以及每种框架的详细介绍

目录 前言1. React2. Vue.js3. Angular4. Bootstrap5. Foundation总结 前言 前端框架是Web开发中不可或缺的工具&#xff0c;它们为开发者提供了丰富的工具和抽象&#xff0c;使得构建复杂的Web应用变得更加容易。当前&#xff0c;前端框架种类繁多&#xff0c;其中一些最受欢…

【全网最全】2024年数学建模国赛A题30页完整建模文档+17页成品论文+保奖matla代码+可视化图表等(后续会更新)

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下的卡片那是获取资料的入口&#xff01; 【全网最全】2024年数学建模国赛A题30页完整建模文档17页成品论文保奖matla代码可视化图表等&#xff08;后续会更新&#xff09;「首先来看看目前已有的资料&#xff0…

应用开发“取经路”,华为应用市场送出全周期服务“助攻”

最近大量国内外玩家被西游神话圈粉&#xff0c;化身游戏人物角色&#xff0c;踏上了充满冒险的取经路。如果让莘莘学子或创业者们&#xff0c;在自己的职业生涯中&#xff0c;也选一个机遇跟挑战并存的角色&#xff0c;“开发者”一定榜上有名。 智能手机和移动互联网的普及&am…