多线程的死锁问题

fly

可重入和不可重入😊😊😊

一个线程针对同一个对象,连续加锁两次,是否会有问题 ~~ 如果没问题,就叫可重入的.如果有问题,就叫不可重入的.

代码示例🍉🍉🍉:

synchronized public void add(){synchronized (this){count++;}
}

解析😍😍😍:
锁对象是this,只要有线程调用add,进入add方法的时候,就会先加锁(能够加锁成功).紧接着又遇到了代码块,再次尝试加锁.站在this 的视角(锁对象),它认为自己已经被另外的线程给占用了,这里的第二次加锁是否要阻塞等待呢?🤔🤔🤔
如果第二次加锁成功,这个锁就是可重入的,Java中的synchronized 是“可重入锁”.
如果第二次加锁会阻塞等待,就是不可重入的,这样的锁称为“不可重入锁”.

这时新的情况就出现了😕😕😕:

场景如下: 一个线程没有释放锁, 然后又尝试再次加锁,这个锁是“不可重入锁”,第二次加锁的时候, 就会阻塞等待,直到第一次的锁被释放, 才能获取到第二个锁,但是释放第一个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无法进行解锁操作,这时候就会死锁.
注: 博主后面的内容会重点讲解死锁.

Java 标准库中的线程安全类🍭🍭🍭

多个线程操作同一个集合类,就需要考虑到线程安全的事情.
1.Java 标准库中很多集合类都是线程不安全的,没有任何加锁措施.
比如:ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,StringBuider.
2.但是还是有一些内置了synchronized加锁的集合类,线程是相对来说,更安全一点的,
比如: Vector(不推荐使用),HashTable(不推荐使用),StringBuffer,ConcurrentHashMap.
3.最后就是String它没有加锁,但是由于是不可变对象,不涉及修改,线程是绝对安全的

问题: 既然加锁了,线程会变得安全一些,为什么集合类都不加上锁了🤔🤔🤔🤔?
原因: (1).加锁这个是有副作用的,会产生额外的时间开销.
(2).对程序猿来说,有更多的选择空间,这些没内置加锁机制的集合类,当没有线程安全问题的时候,就可以放心用,当有线程安全问题的时候,可以手动加锁.但是像StringBuffer这些内置synchronized加锁的结合类,就没有这种选择,这也是为什么是Vector,HashTable不推荐使用的原因之一.

死锁❤️❤️❤️

~~ 死锁是一个非常影响我们幸福感问题.
一旦程序出现死锁,就会导致线程就跪了,无法继续执行后续工作了,程序势必会有严重bug,在我们写代码的时候,不经意间,就会写出死锁代码,并且这玩意还不容易测试出来.

死锁的三个典型情况🍁🍁🍁

1.一个线程,一把锁,连续加锁两次🍒🍒🍒

~~ 如果锁是“不可重入锁”,就会死锁.
注: Java里 synchronized 和 ReentrantLock 都是“可重入锁”.C++,Python,操作系统原生的加锁API都是不可重入的.

2.两个线程两把锁🍎🍎🍎

~~ 线程 t1 和线程 t2 各自先针对锁A和锁B加锁,再尝试获取对方的锁.
例子: (1)可以理解为你把家里门的钥匙锁在车里了,而车钥匙锁在家里了,最后,你不仅车开不了呢,家也回不去了
(2)有一个东北人和一个陕西人正坐在饺子馆的同一个餐桌上准备吃饺子,东北人吃饺子,喜欢蘸酱油,但他面前只有一瓶醋;陕西人吃饺子,喜欢蘸醋,但不巧的是他面前只有酱油;东北人说: “兄弟,你把酱油给我,我用完了之后给你醋”,但是陕西人也说:”老兄,你把醋给我,我用完了之后给你酱油”.假设哈,注意这是假设(现实生活一般不可能会这样)!!!如果这两人互不相让,此时就僵持住了.
例子2的代码:

public class ThreadDemo14 {public static void main(String[] args) {Object jiangyou = new Object();// jiangyou => 酱油Object cu = new Object();// cu => 醋Thread dongbeiren = new Thread(() -> {// dongbeiren => 东北人synchronized (cu) {try {Thread.sleep(1000);// 确保两个线程都先把第一个锁拿到 => 线程是抢占式执行的} catch (InterruptedException e) {e.printStackTrace();}synchronized (jiangyou) {System.out.println("东北人把醋和酱油都拿到了");}}});Thread shanxiren = new Thread(() -> {// shanxiren => 陕西人synchronized (jiangyou) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (cu) {System.out.println("陕西人把酱油和醋都拿到了");}}});dongbeiren.start();shanxiren.start();}
}

运行结果:

image-20230926104443616

使用 jconsole 查看线程的情况

image-20230926112333971

注: 针对死锁问题,是需要借助像 jconsole 这样的工具进行定位,看线程的状态和调用栈,从而分析出代码是在哪里死锁了.
image-20230926113122179

image-20230926113420416

3.多个线程,多把锁🍀🍀🍀

哲学家就餐问题 ~~ 教科书上的经典案例
image-20230926124320039

想要解决死锁问题,就得分析一下死锁的形成.

死锁的四个必要条件(缺一不可)🌸🌸🌸:

1.互斥使用: 一个线程拿到一把锁之后,另一个线程就不能使用了(锁的基本特性).
2.不可抢占: 一个线程拿到锁之后,必须是这个线程主动释放,其它线程就获取不到了(墙角是挖不了滴).
3.请求和保持: 线程1拿到锁A之后,再去尝试获取锁B,这时线程1的A这把锁还是保持的,不会因为获取锁B就把锁A给释放了.
个人理解“吃着碗里的,惦记锅里的”,一个男生有了女朋友后,还去和其他女生搞暧昧,追求其他女生(渣男).
4.循环等待: 线程1尝试获取到锁A和锁B,线程2尝试获取到锁B和锁A,线程1在获取B的时候等待线程2的释放;同时,线程2在获取A的时候等待线程1释放锁A(或者可以理解家钥匙锁车里了,车钥匙锁家里了).

注: 条件1,2,3都是锁的基本特性,对于,synchronized这把锁来说,是无法改对.循环等待是这四个条件中唯一一个和代码结构相关的,也是作为程序猿的我们可以控制的.

解决死锁的方法其实很简单,就是破解循环等待这个必要条件🍃🍃🍃🍃:
针对锁进行编号,在需要同时获取多把锁的时候,约定加锁顺序,务必先对小的编号加锁,后对大的编号加锁(这是解决死锁,最简单可靠的办法).
注: 解决死锁,还有个银行家算法,本质上是对资源的更合理的分配,比较复杂,不适合在实际开发中使用,但是是学校操作系统课的期末考试必考题,建议上这门课的时候认真听一下,博主不在这讲解,因为我也不太理解,讲不来,(●’◡’●).
代码:

public class ThreadDemo14 {public static void main(String[] args) {// 假设 jiangyou 是 1 号, cu 是 2 号, 约定先拿小的, 后拿大的Object jiangyou = new Object();// jiangyou => 酱油Object cu = new Object();// cu => 醋Thread dongbeiren = new Thread(() -> {// dongbeiren => 东北人synchronized (cu) {try {Thread.sleep(1000);// 确保两个线程都先把第一个锁拿到 => 线程是抢占式执行的} catch (InterruptedException e) {e.printStackTrace();}synchronized (jiangyou) {System.out.println("东北人把醋和酱油都拿到了");}}});Thread shanxiren = new Thread(() -> {// shanxiren => 陕西人synchronized (cu) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (jiangyou) {System.out.println("陕西人把酱油和醋都拿到了");}}});dongbeiren.start();shanxiren.start();}
}

运行结果:

image-20230926143143122

多线程当前的知识点梳理🎀🎀🎀

  1. 线程的基本概念.轻量,共享资源,节省了资申请的开销

  2. Thread类的使用

    1. 创建
      1. 继承Thread,重写run
      2. 实现Runnable,重写run
      3. 匿名内部类,继承Thread,重写run
      4. 匿名内部类,继承实现Runnable,重写run
      5. lambda表达式
    2. 终止
      1. 自己定义一个标志位
      2. 使用线程内置的标志位
        1. isInterruptted(),判断标志位是否为true
        2. interrupt(),设置标志位为true,还能以异常的方式唤醒sleep
      3. 其它线程只能通知该线程要终止了,这个线程是否真的终止,是它自己的事情~~
    3. 等待
      1. join ~~ 在线程a中,调用b.join(),此时就是线程a等待线程b结束.
      2. 阻塞
    4. 获取线程引用 ~~ Thread.currentThread();
    5. 休眠 ~~ sleep
  3. 线程状态

    1. NEW ~~ 初创状况
    2. TERMINATED ~~ 消亡状况
    3. RUNNABLE ~~ 就绪状况
    4. TIMED_WAITING ~~ 等待一段时间
    5. WAITING
    6. BLOCKED
  4. 线程安全

    一个多线程实际的执行顺序有多种变数 ~~ 线程安全就是在所有的变数下,都能够运行正确!!!

    1. 抢占式执行,随机调度

    2. 多个线程同时修改同一个变量

    3. 修改操作不是原子性的 ~~ 加锁(synchronized)

    4. 内存可见性问题

    5. 指令重拍序

  • 加锁 ~~ synchronized

关于加锁

  1. 修饰方法

    • 普通方法 ~~ 把锁加到this对象上
    • 静态方法 ~~ 把锁加到类对象上
  2. 修饰代码块 ~~ 手动指定加到那个对象上

  3. 锁对象

    1. 两个线程,针对同一个对象加锁,会发生锁冲突/锁竞争(产生阻塞等待)
    2. 两个线程,针对不同的对象加锁,不会有任何锁冲突
  4. 死锁

    1. 死锁的概念

    2. 死锁的三个典型情况

      1. 一个现场一把锁.连续加锁两次

      2. 两个线程两把锁,分别获取对方的锁

      3. N个线程,M把锁

    3. 可重入和不可重入

      • 线程针对同一个对象,连续加锁二次,是否会死锁

      • 会死锁,就叫可重入,不会死锁,就叫不可重入的.

      • 注: synchronized 是可重入的

    4. 死锁的四个必要条件

      • 最核心的就是“循环等待”

      • 解决: 在针对多把锁加锁的时候,约定好锁的顺序

    5. 如何破除死锁

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

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

相关文章

Android AMS——创建APP进程(五)

接上一篇,在 ActivityTaskSupervisor 中会判断进程是否存在,如果进程不存在,则会创建进程,执行 startProcessAsync() 方法。如果进程存在,则执行 realStartActivityLocked() 方法。在APP 的启动时,进程是不存在的。所以我们先来分析一下进程不存在的情况。 一、创建进程…

clickhouse分组排序,行号,取特定数量数据

文章目录 1、源数据2、生成数组2.1 groupArray 分组合并为数组2.2 arrayEnumerate 标记数据 3、rank()、row_number()3.1 说明3.2 使用 目前应用很多需求设计对数据分组并去特定数量的数据; clickhouse 新版本增加了row_number(),rank() 函数&#xff0c…

vue wangEditor富文本编辑器 默认显示与自定义工具栏配置

1.vue 显示wangEditor富文本编辑器 <template><div style"border: 1px solid #ccc;"><Toolbar style"border-bottom: 1px solid #ccc" :editor"editor" :defaultConfig"toolbarConfig" :mode"mode"/><…

在线商城项目EShop【ListView、adapter】

要求如下&#xff1a; 1、创建在线商城项目EShop&#xff1b; 2、修改布局文件activity_main.xml&#xff0c;使用LineaLayout和ListView创建商品列表UI&#xff1b; 3、创建列表项布局list_item.xml&#xff0c;设计UI用于显示商品图标、名称和价格信息&#xff1b; 4、创…

流媒体播放器EasyPlayer.js无法播放H.265的情况是什么原因?该如何解决?

H5无插件流媒体播放器EasyPlayer属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;可支持H.264与H.265编码格式&#xff0c;性能稳定、播放流畅&#xff0c;能支持WebSocket-FLV、HTTP-FLV&#xff0c;HLS&#xff08;m3u8&#…

基于 ECDSA(椭圆曲线数字签名算法)生成 JWT Token

文章目录 1. 为什么选择 ECDSA&#xff08;椭圆曲线数字签名算法&#xff09;2. 生成签名公私钥2.1. 使用 Open SSL 生成公私钥2.2. Java 语言 pkcs8 格式私钥转换 3. SpringBoot 分环境配置密钥3.1. yaml 文件配置3.2. 密钥配置类 4. 使用 auth0 生成 JWT Token4.1. 依赖4.2. …

外卖小程序开发指南:打造完美的点餐体验

第一步&#xff1a;项目设置和初始化 首先&#xff0c;您需要选择一个适合您的开发平台&#xff0c;例如微信小程序、支付宝小程序或其他移动应用平台。接下来&#xff0c;创建一个新的小程序项目&#xff0c;并初始化所需的文件和目录。 示例代码&#xff08;微信小程序&am…

自定义类型:结构体,枚举,联合

自定义类型&#xff1a;结构体&#xff0c;枚举&#xff0c;联合 前言&#xff1a;结构体1.结构体类型的声明2.结构的自引用3.结构体变量的定义和初始化4.结构体内存对齐5.结构体传参6.结构体实现位段&#xff08;位段的填充&可移植性&#xff09; 枚举1.枚举类型的定义2.枚…

多维时序 | MATLAB实现WOA-CNN-BiGRU-Attention多变量时间序列预测(SE注意力机制)

多维时序 | MATLAB实现WOA-CNN-BiGRU-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09; 目录 多维时序 | MATLAB实现WOA-CNN-BiGRU-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基本…

string类模拟实现——C++

一、构造与析构 1.构造函数 构造函数需要尽可能将成员在初始化列表中初始化&#xff0c;string类的成员这里自定义的和顺序表相似&#xff0c;有_str , _size , _capacity , 以及一个静态成员 npos &#xff0c;构造函数这里实现两种&#xff0c;一种是传参为常量字符串的&am…

企业架构相关

数据架构的作用首先是找到所有的业务对象 和数据对象。 在数据对象分析里面有一个重点就是主数据识别和分析。

Python接口自动化搭建过程,含request请求封装

接口测试自动化好处 显而易见的好处就是解放双手&#x1f600;。 可以在短时间内自动执行大量的测试用例通过参数化和数据驱动的方式进行测试数据的变化&#xff0c;提高测试覆盖范围快速反馈测试执行结果和报告支持持续集成和持续交付的流程 使用Requestspytestallure搭建测…

【蓝桥杯选拔赛真题64】Scratch神奇画笔 少儿编程scratch图形化编程 蓝桥杯选拔赛真题解析

scratch神奇画笔 第十四届青少年蓝桥杯scratch编程选拔赛Stema比赛真题 一、题目要求 编程实现 1). 运行程序,背景如图所示; 2). 等待1秒后切换到下一个角色、背景(画板中简笔画为参照绘制样例); 3). 按下鼠标左键,画笔隐藏并跟随鼠标移动同时在空白处画线(松开鼠标…

Java skill - 动态指定feign的访问地址

Java skill - 动态指定feign的访问地址 场景编码新增feign传参类新增调用内部服务的feign接口feign配置类逻辑修改 大坑 场景 在有下沉节点项目的前提下&#xff0c;使用feign调用内部服务的时候&#xff0c;在redis查询需要调用的内部服务的elb地址并调用 编码 新增feign传…

除了 MySQL,这些数据库你都认识么?

什么是数据库&#xff1f; 这个问题相信对学编程的朋友们来说过于简单了&#xff0c;大家想必都是增删改查的好手。 但如果让你说出 10 种不同类型的数据库&#xff0c;阁下该如何应对&#xff1f; 这篇文章&#xff0c;是对数据库技术的一个小科普&#xff0c;希望能帮大家…

通俗易懂了解大语言模型LLM发展历程

1.大语言模型研究路程 NLP的发展阶段大致可以分为以下几个阶段&#xff1a; 词向量词嵌入embedding句向量和全文向量理解上下文超大模型与模型统一 1.1词向量 将自然语言的词使用向量表示&#xff0c;一般构造词语字典&#xff0c;然后使用one-hot表示。   例如2个单词&…

STL算术生成和集合算法

目录 算术生成算法accumulate 算术生成算法file 常用集合算法 常用集合算法 常用集合算法set_difference 算术生成算法accumulate 算术生成算法属于小型算法&#xff0c;使用时包含的头文件为 include <numeric> accumulate(iterator beg, iterator end, value); …

【Java】JSONArray详解

JSONArray是JSON数据格式中的一种数据结构&#xff0c;主要用于存储和操作有序的元素集合。本文将对JSONArray进行详细介绍&#xff0c;包括其定义、使用方法和实际应用场景。 定义 JSONArray是一种有序的元素集合&#xff0c;可以包含任意类型的数据&#xff0c;如字符串、数…

初识java

目录 1. cmd(命令提示符) 1. 什么是cmd 2. cmd常用命令 1. 打开cmd 2.常用命令 2. 什么是java 1. 为什么学Java? 2. JDK的下载和安装 3.第一个java程序(重点) 1.使用记事本编写程序 2.翻译文件(编译) 3.运行文件 4.配置环境变量 1.为什么要配置环境变量 2.配置…

华纳云:linux怎么查看Raid磁盘阵列信息

要查看Linux系统中的RAID磁盘阵列信息&#xff0c;您可以使用以下命令和工具&#xff1a; 使用mdadm工具查看RAID信息&#xff1a; mdadm是用于管理Linux软件RAID&#xff08;Redundant Array of Independent Disks&#xff09;的命令行工具。您可以使用以下命令来查看RAID磁…