多线程的死锁问题

news2024/11/26 13:42:00

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.coloradmin.cn/o/1042677.html

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

相关文章

Mixin和HTX遭黑客攻击!后者全赔,前者只赔50%引投资者不满?

资产安全一直都是区块链老生常谈的话题,而最近一系列安全事件频发引起了投资者的担忧,先是V神的推特账号被盗,再是亿万富翁马克库班 (Mark Cuban) 的小狐狸钱包被攻击,如今,黑客又盯上了承载大量资金的加密机构。 9月2…

如何提取音频中的纯人声?新手也能快速掌握

在数字媒体时代,无论是音乐、电影还是短视频制作,音频处理都是一个不可或缺的技能,尤其是人声提取部分,今天就来教大家怎样在一段音频中把人声部分提取出来,新手也能快速掌握! 第一步:打开【音分…

(手撕)快速排序 ----->c语言实现 +图解

目录 目录: 1:快速排序的思想 2:快速排序的三种形式 3:快速排序的优化方法 前言:快速排序是一种非常重要的排序我们需要掌握它,当然肯定也相比前面的那些排序有一定的难度,但是相信本篇文章会让你对快排有重新的理解,…

vue3 + mark.js | 实现文字标注功能

页面效果 具体实现 新增 1、监听鼠标抬起事件,通过window.getSelection()方法获取鼠标用户选择的文本范围或光标的当前位置。2、通过 选中的文字长度是否大于0或window.getSelection().isCollapsed (返回一个布尔值用于描述选区的起始点和终止点是否位于一个位置&…

TensorFlow入门(三、TensorFlow模型的运行机制)

TensorFlow通过"图"和会话的方式分离了计算的定义和执行,即它的运行机制是"定义"与"运行"相分离的。从操作层面可以把它抽象成两种:模型构建和模型运行。 TensorFlow模型中的几个概念: ①张量(tensor):数据,即某一类型的多维数组 ②变量(Vari…

Wi-Fi直连分享:Android设备间的高速连接

Wi-Fi直连分享:Android设备间的高速连接 引言 随着无线局域网(Wi-Fi)的普及和发展,使用Wi-Fi直连技术(P2P)在没有中间接入点的情况下实现设备间直接互联成为可能。通过Wi-Fi直连,具备相应硬件…

链动2+1模式:如何用二级分销打造高效团队,实现销量突破

你是否想要拥有一个高效的团队,让你的销量快速提升?你是否厌倦了传统的多层级分销模式,觉得它太复杂、太难管理、太不合规?你是否想要找到一种简单、合理、合法的商业模式,让你的收益稳定、可持续、可复制?…

【深度学习实验】卷积神经网络(三):自定义二维卷积神经网络:步长和填充、输入输出通道

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. 步长、填充 a. 二维互相关运算(corr2d) b. 二维卷积层类(Conv2D) c. 模型测试 d. 代码整合 2. 输入输出通道 a…

前缀树-Trie树

前缀树—Trie树,也叫作“单词查找树”、“字典树” 它属于多叉树结构,典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利…

【前段基础入门之】=>玩转【CSS】开篇章!

目录 CSS 的简介:CSS的编写位置行内样式内部样式外部样式 样式表的优先级CSS语法规范: 总结: CSS 的简介: 层叠样式表(Cascading Style Sheets,缩写为 CSS)是一种样式表语言,用来描述…

前端项目练习(练习-007-typescript-02)

学习前,首先,创建一个web-007项目,内容和web-006一样。(注意将package.json中的name改为web-007) 前面的例子,我们使用了nodejswebpack,成功创建了包含html,ts,css三个文…

【.net core】使用nssm发布WEB项目

nssm下载地址:NSSM - the Non-Sucking Service Manager 配置方式 修改服务在nssm工具下输入命令:nssm edit jntyjr 其中 jntyjr为添加服务时设置的Service name nssm可以设置任何以参数启动的应用程序以服务形式启动,通过设置参数内容启动服务 以上配置等同于执行…

ReferenceError: primordials is not defined错误解决

问题场景: 从github上拉了一个项目,想要学习一下,在起服务的时候出现了这个问题。 造成的原因: gulp 与 node 版本起冲突。 1)首先,安装 gulp,查看版本; npm install gulp -g g…

如何设计科研问卷?

问卷研究法的最大特点在于能在较短时间内调查很多研究对象取得大量的资料,并能对资料进行数量化处理,经济省时,因此是教育研究中使用频率较高、用途较广泛的一种研究方法。问卷研究法的关键在于设计一份信度、效度较高,内容合理的…

二维码怎么分解成链接?线上快速解码教学

怎么分解二维码呢?有些时候我们需要将二维码图片分解成链接使用,所以想要使用解码功能一般都需要通过二维码生成器工具来完成。那么如何在线将二维码分解成链接呢,可能有些小伙伴还不知道怎么操作,下面就给大家分享一下免费二维码…

较真儿学源码系列-PowerJob时间轮源码分析

PowerJob版本:4.3.2-main。 之前分析过PowerJob的启动流程源码,感兴趣的可以查看《较真儿学源码系列-PowerJob启动流程源码分析》 1 简介 试想一下,如果此时有一个需要延迟3s执行的任务,你会怎么实现呢?一种常规的思路…

洗地机哪个牌子好用又实惠?口碑最好的洗地机推荐

智能技术飞速发展的时代,扫地机器人这类智能家电其实也在顺应潮流和用户需求,不断更新迭代。暂且不说市面上现有多少个洗地机品牌,单单一个洗地机品牌旗下,其实每年都会有多个系列的新品亮相,我们面对的选择多了&#…

Python交叉验证实现

目录 <font colorblue size4 face"楷体">HoldOut 交叉验证<font colorred size4 face"楷体">K 折交叉验证<font colorblue size4 face"楷体">分层 K 折交叉验证<font colorblue size4 face"楷体">Leave P Out…

融云 CallPlus + X,通话场景一站式解决方案

融云近期上线的 CallPlus SDK&#xff0c;针对音视频呼叫场景单独设计后端服务 Call Server&#xff0c;信令延时低至 150ms&#xff0c;确保各端计时准确、一致&#xff1b;上线了音视频通话互转、灵活的多人通话、通话记录管理能力等功能。关注【融云全球互联网通信云】了解更…

掌动智能兼容性测试有哪些优势

兼容性测试为企业带来市场竞争优势&#xff0c;并提高用户满意度。在软件开发过程中&#xff0c;将兼容性测试作为一个重要的环节&#xff0c;将为企业的成功和用户满意度打下坚实的基础。那么&#xff0c;掌动智能兼容性测试的具体优势是什么?下面&#xff0c;就来看看具体介…