JavaEE之多线程编程:5. 死锁(详解!!!)

这篇具有很好参考价值的文章主要介绍了JavaEE之多线程编程:5. 死锁(详解!!!)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、死锁是什么

死锁是这样的一种情形:多个同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
JavaEE之多线程编程:5. 死锁(详解!!!),JavaEE,java-ee,java,数据库,算法,intellij idea,开发语言,学习方法

【举个例子理解死锁】
张三李四两人去吃饺子,吃饺子需要酱油和醋。
张三抄起了酱油瓶, 李四抄起了醋瓶。
张三:你先把醋瓶给我,我用完了就把酱油瓶给你。
李四:你先把酱油瓶给我,我用完了就把醋瓶给你。
如果这俩人彼此之间互不相让,就构成了死锁。
酱油和醋相当于是两把锁,这两个人就是两个线程。

二、关于死锁的三种形式

  1. 一个线程,针对同一把锁连续加锁两次,如果是不可重入锁,就死锁了。
    关于可重入锁:指的是一个线程连续针对一把锁,加锁了两次且不会出现死锁,满足这个要求就是可重入锁。
  2. 两个线程,两把锁(此时无论是不是不可重入锁,都会死锁)。
    如:有两个线程 t1、t2,有两把锁 A、B
    ① t1 获取锁 A,t2 获取锁 B;
    ② t1 尝试获取锁 B,t2 尝试获取锁 A。
    此时就会阻塞,产生死锁。
package Thread;

/**
 * @Author : tipper
 * @Description : 死锁的情况
 */
public class Demo4 {
    //锁1
    private static Object locker1 = new Object();
    //锁2
    private static Object locker2 = new Object();

    public static void main(String[] args) {
        //线程1
        Thread t1 = new Thread(()->{
            //加锁
            synchronized (locker1) {
                //此处的sleep很重要,要确保t1和t2都分别拿到一把锁之后。再进行后续动作。
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t1 加锁成功!");
                }
            }
        });
        //线程2
        Thread t2 = new Thread(()->{
            //加锁
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker1) {
                    System.out.println("t2 加锁成功!");
                }
            }
        });
    }
t1.start();
t2.start();
}

//输出为空且一直在执行

上述代码中,很明显什么都没打印,两个线程都没有获取成功第二把锁。
在此时,死锁代码中,两个 synchronized 是嵌套关系,不是并列关系,嵌套关系说明是在占用一把锁的前提下,获取另一把锁;而并列关系则是先释放前面的锁,再获取下一把锁,这样就不会死锁了。

  1. N个线程,M把锁。(相当于第2种情况的扩充)
    此时是更容易出现死锁的情况了。
    一个经典的描述N个线程M把锁死锁的模型,哲学家就餐问题。

【哲学家就餐问题】
JavaEE之多线程编程:5. 死锁(详解!!!),JavaEE,java-ee,java,数据库,算法,intellij idea,开发语言,学习方法

① 每个哲学家都会做两件事:思考人生,放下筷子 和 拿起左右两侧的两根筷子开始吃面条(先拿起左边,再拿起右边);
② 哲学家什么时候吃面条和思考人生都是随机的;
③ 哲学家吃面条吃多久吃完也是随机的;
④ 哲学家吃面条的过程中,会有左右相邻的哲学家如果也想吃面条,就要阻塞等待。

基于上述规则,通常情况下,整个系统可以良好运转,但是极端情况下会出现问题!
比如,同一时刻,五个哲学家都想吃面条,同时拿起左手的筷子,然后再尝试拿右手的筷子,就会发现右手的筷子都被占用了,哲学家们互不相让,这个时候就形成了死锁
JavaEE之多线程编程:5. 死锁(详解!!!),JavaEE,java-ee,java,数据库,算法,intellij idea,开发语言,学习方法
死锁是一种严重的BUG!会导致程序的线程“卡死”,无法正常工作!

三、如何避免死锁

死锁产生的四个必要条件:

  • 互斥使用(锁的基本特性):当一个线程持有一把锁之后,另一个线程想要取得锁,就要阻塞等待;
  • 不可抢占(锁的基本特性):资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。(当锁已经被 线程1 拿到之后,线程2 只能等 线程1 主动释放,不能强行抢过来)
  • 请求和保持(代码结构):当资源请求者在请求其他的资源的同时保持对原有资源的占有。(一个线程尝试获取多把锁,先拿到 锁1 之后,再尝试获取 锁2,获取的时候,锁1 不会释放)。
  • 循环等待:即存在一个等待队列,P1占有P2的资源,P2占有P3的资源,P3占有P1的资源,等待的依赖关系,这样就形成了一个等待环路。

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

其中最容易破坏的就是“循环等待”。“互斥使用”、“不可抢占”是锁本身的特性,破坏不了,对于“请求保持”来说,调整代码结构,避免编写“锁嵌套”逻辑。这个方案不一定好用,有的需求可能就是需要进行这种,获取多个锁再操作。

【破坏循环等待】
最常用的一种死锁组织技术就是锁排序。约定加锁的顺序,就可以避免循环等待。
假设有 N 个线程尝试获取 M 把锁,就可以针对 M 把锁进行编号(1,2,3…M)。
N 个线程尝试获取锁的时候,都按照固定的按编号由小到大顺序来获取锁。这样就可以避免环路等待。

比如:哲学家就餐问题,约定每个哲学家都是先拿起编号小的筷子,后拿起编号大的筷子,此时循环等待就破除了,上述系统就不会再出现死锁了。
JavaEE之多线程编程:5. 死锁(详解!!!),JavaEE,java-ee,java,数据库,算法,intellij idea,开发语言,学习方法

【可能产生环路等待的代码】
两个线程对于加锁的顺序没有约定,就容易产生环路等待。

package Thread;
public class Demo4 {
    //锁1
    private static Object locker1 = new Object();
    //锁2
    private static Object locker2 = new Object();

    public static void main(String[] args) {
        //线程1
        Thread t1 = new Thread(()->{
            //加锁
            synchronized (locker1) {
                //此处的sleep很重要,要确保t1和t2都分别拿到一把锁之后。再进行后续动作。
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
                synchronized (locker2) {
                    System.out.println("t1 加锁成功!");
                }


        //线程2
        Thread t2 = new Thread(()->{
            //加锁
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
                synchronized (locker1) {
                    System.out.println("t2 加锁成功!");
                }
    }
}

//运行结果:
此时一直运行


【不会产生环路等待的代码】
约定号先获取 locker1,再获取 locker2,就不会环路等待。文章来源地址https://www.toymoban.com/news/detail-824088.html

package Thread;

/**
 * @Author : tipper
 * @Description : 死锁的情况
 */
public class Demo4 {
    //锁1
    private static Object locker1 = new Object();
    //锁2
    private static Object locker2 = new Object();

    public static void main(String[] args) {
        //线程1
        Thread t1 = new Thread(()->{
            //加锁
            synchronized (locker1) {
                //此处的sleep很重要,要确保t1和t2都分别拿到一把锁之后。再进行后续动作。
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t1 加锁成功!");
                }
            }
        });

        //线程2
        Thread t2 = new Thread(()->{
            //加锁
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t2 加锁成功!");
                }
            }
        });
        t1.start();
        t2.start();
    }

}
//运行结果:
t1 加锁成功!
t2 加锁成功!

到了这里,关于JavaEE之多线程编程:5. 死锁(详解!!!)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【JavaEE】多线程之线程安全(synchronized篇),死锁问题

    线程安全问题 观察线程不安全 线程安全问题的原因  从原子性入手解决线程安全问题 ——synchronized synchronized的使用方法  synchronized的互斥性和可重入性 死锁 死锁的三个典型情况  死锁的四个必要条件  破除死锁 在前面的章节中,我们也了解到多线程为我们的程序带来了

    2024年02月01日
    浏览(57)
  • 互联网编程之多线程/线程池TCP服务器端程序设计

    目录 需求 多线程TCP服务器 线程池TCP服务器 测试 日志模块 多线程TCP服务器(30分): 设计编写一个TCP服务器端程序,需使用多线程处理客户端的连接请求。客户端与服务器端之间的通信内容,以及服务器端的处理功能等可自由设计拓展,无特别限制和要求。 线程池TCP服务器

    2024年02月11日
    浏览(44)
  • 【JavaEE基础学习打卡02】是时候了解Java EE了!

    📜 本系列教程适用于 Java Web 初学者、爱好者,小白白。我们的天赋并不高,可贵在努力,坚持不放弃。坚信量最终引发质变,厚积薄发。 🚀 文中白话居多,尽量以小白视角呈现,帮助大家快速入门。 🎅 我是 蜗牛老师 ,之前网名是 Ongoing蜗牛 ,人如其名,干啥都慢,所

    2024年02月12日
    浏览(48)
  • 【JavaEE基础学习打卡03】Java EE 平台有哪些内容?

    📜 本系列教程适用于Java Web初学者、爱好者,小白白。我们的天赋并不高,可贵在努力,坚持不放弃。坚信量最终引发质变,厚积薄发。 🚀 文中白话居多,尽量以小白视角呈现,帮助大家快速入门。 🎅 我是 蜗牛老师 ,之前网名是 Ongoing蜗牛 ,人如其名,干啥都慢,所以

    2024年02月12日
    浏览(47)
  • 【并发编程】多线程安全问题,如何避免死锁

    从今天开始阿Q将陆续更新 java并发编程专栏 ,期待您的订阅。 在系统学习线程之前,我们先来了解一下它的概念,与经常提到的进程做个对比,方便记忆。 线程和进程是操作系统中的两个重要概念,它们都代表了程序运行时的执行单位,它们的出现是为了更好地管理计算机

    2024年02月11日
    浏览(51)
  • JAVA之多线程

    进程是指运行中的应用程序,每一个进程都有自己独立的内存空间; 线程是指进程中的一个执行流程,有时也称为执行情景; 一个进程可以由多个线程组成,即在一个进程中可以同时运行多个不同的线程,它们分别执行不同的任务; 当进程内的多个线程同时运行,这种运行

    2024年02月07日
    浏览(41)
  • Java之多线程进阶

    目录 一.上节内容复习 1.线程池的实现 2.自定义一个线程池,构造方法的参数及含义 3.线程池的工作原理 4.拒绝策略 5.为什么不推荐系统提供的线程池 二.常见的锁策略 1.乐观锁和悲观锁 2.轻量级锁和重量级锁 3.读写锁和普通互斥锁 4.自旋锁和挂起等待锁 5.可重入锁和不可重入

    2024年02月05日
    浏览(49)
  • Java之多线程初阶

    目录 一.进程和线程 1.什么是进程 2.并发,并行和串行 3.线程和多线程 4.进程和线程的区别(重点) 5.多线程的优点 6.多线程的缺点 二.线程的创建 1.继承Thread类 2.实现Runnable接口重写run()方法 3.通过匿名内部类的方式创建Thread和实现Runnable 4.通过Lambda表达式来实现一个线程 三.查看

    2024年02月05日
    浏览(43)
  • Java之多线程初阶2

    目录 一.上节内容复习 1.进程和线程的区别 2.创建线程的四种方式 二.多线程的优点的代码展示 1.多线程的优点 2.代码实现 三.Thread类常用的方法 1.Thread类中的构造方法 2.Thread类中的属性 1.为线程命名并获取线程的名字 2.演示isDaemon() 3.演示isAlive() 4.演示getState() 3.Thread类中的方

    2024年02月04日
    浏览(49)
  • java死锁、线程状态、线程通信、线程池

    java实现多线程: [1]继承Thread类并重写run方法 [2]实现Runnable接口 线程Thread中常用的方法: setName(): Thread.currentThread().getName(): ​ static void sleep(); static void yield(): join(): setDeamon()设置后台线程 线程安全问题: ---当多个线程共享同一个资源时,对该资源的操作就会出现线程安全问题。

    2024年02月13日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包