Java多线程中的死锁详解

 更新时间:2023年08月26日 08:29:48   作者:一只爱打拳的程序猿  
这篇文章主要介绍了Java多线程中的死锁详解,死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,如果不提前预防或外界干扰,这些线程将无法执行下去,需要的朋友可以参考下

前言

死锁(Deadlock)是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象

如果不提前预防或外界干扰,这些线程将无法执行下去。

因此,本篇博文讲解造成死锁的原因以及解决方案。

1. 造成死锁的原因

死锁是多个线程在相互等待对方所占有的锁才能继续执行下去,造成线程都被阻塞都无法执行下去,也释放不了线程自己的锁资源,造成僵局。

举例说明:

我和朋友去拉面馆吃面,我先拿起了辣椒他拿起了醋。此时他想要辣椒、我想要醋,但我俩谁也不让谁。

我对他说:你先把辣椒给我,我用完了就把醋给你。他对我说:你先把醋给我,我用完了就把辣椒给你。

我和他也不让谁,最后导致死锁。在上述例子中,辣椒和醋就是两个锁,我和他就是两个线程。关于死锁会引申的一个哲学家就餐问题。

2. 哲学家就餐问题

哲学家就餐问题是一个经典的并发编程问题,它描述的是五个哲学家围坐在一张圆形餐桌旁,每个哲学家面前有一碗饭和一只筷子,碗和筷子都是共享资源。

哲学家需要用一双筷子来吃饭,但是只有相邻的两个哲学家之间有一只筷子,因此他们必须在餐桌上竞争筷子,才能拿到一双筷子吃饭。

这个问题会引发死锁(Deadlock)和饥饿(Starvation)等并发编程中的经典问题。

如果所有哲学家同时都想获取餐具,但每个哲学家只能等待旁边的哲学家放下筷子才能获取餐具,这可能会导致死锁。

如果所有哲学家只有在其他人都吃到饭才能拿到自己的餐具,那么有些哲学家可能会因为等待时间太长而饥饿。

代码案例:

有两个哲学家在进行就餐,哲学家1获取到筷子1后继续尝试获取筷子2,哲学家2获取到筷子2后继续尝试获取筷子1,造成了死锁状态。

public class DeadlockDemo {
        public static void main(String[] args) {
            Object obj1 = new Object();//筷子1
            Object obj2 = new Object();//筷子2
            // 线程1获取obj1,然后等待obj2
            Thread thread1 = new Thread(() -> {
                synchronized (obj1) {
                    System.out.println(Thread.currentThread().getName() + " 获取 obj1锁");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj2) {
                        System.out.println(Thread.currentThread().getName() + " 获取 obj2锁");
                    }
                }
            }, "哲学家 1");
            // 线程2获取obj2,然后等待obj1
            Thread thread2 = new Thread(() -> {
                synchronized (obj2) {
                    System.out.println(Thread.currentThread().getName() + " 获取 obj2锁");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj1) {
                        System.out.println(Thread.currentThread().getName() + " 获取 obj1锁");
                    }
                }
            }, "哲学家 2");
            t1.start();
            t2.start();
        }
}

运行后打印:

上述代码,中 thread1 是哲学家1,thread2 是哲学家2,obj1 锁和 obj2 锁分别代表两只筷子。

哲学家1 占用了 obj1 锁,哲学家2 占用了 obj2 锁。

此时 哲学家1 在拥有 obj1 时获取 obj2 是获取不到的,哲学家2 在拥有 obj2 时获取 obj1 也是获取不到的。

这样就会造成死锁。也就是本篇文章开头所说的,一个线程处于永久获取到锁的状态,其他线程无限阻塞等待,这就造成了死锁。

2.1 造成死锁的原因

  • 互斥:一个资源只能被一个进程独占,即该资源只能被一个进程占有或使用。
  • 请求和保持:进程已经保持了至少一个资源,并还在尝试获取其他进程保持的资源。
  • 不可抢占:某个资源只能由占有它的线程释放,不能被其他线程强制占有或抢占。
  • 循环等待:假设一个队列有{t1,t2,t3}这三个线程,t1 等待 t2 所占资源,t2 等待 t3 所占资源,t3 等待 t1 所占资源,这样就会造成循环等待。 

2.2 解决死锁问题

解决死锁问题,最好的方式就是预防死锁。我们把锁按照顺序进行编号,并且约定每个线程在获取多把锁的时候必须先获取编号小的,后获取编号大的。这样就不会形成死锁了。

因为,最先获取编号小锁的线程会优先完成相应任务。轮到其他线程再获取小编号锁时,锁就处于空闲状态了。

代码案例:

public class DeadlockDemo {
        public static void main(String[] args) {
            Object obj1 = new Object();//筷子1
            Object obj2 = new Object();//筷子2
            Thread t1 = new Thread(() -> {
                synchronized (obj1) {
                    System.out.println(Thread.currentThread().getName() + " 获取 obj1锁");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj2) {
                        System.out.println(Thread.currentThread().getName() + " 获取 obj2锁");
                    }
                }
            }, "哲学家 1");
            Thread t2 = new Thread(() -> {
                synchronized (obj1) {
                    System.out.println(Thread.currentThread().getName() + " 获取 obj2锁");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj2) {
                        System.out.println(Thread.currentThread().getName() + " 获取 obj1锁");
                    }
                }
            }, "哲学家 2");
            t1.start();
            t2.start();
        }
}

运行后打印:

在上述代码中,我们创建了两个锁对象 obj1 和 obj2 分别代表两只筷子,并创建两个线程 thread1 和 thread 2 分别代表两个哲学家。

当 哲学家1 首先获取到 obj1 后,由于 obj2 并未线程占用,则立马获取 obj2 ,获得了两双筷子,就餐后释放 obj1 和 obj2。

这时 哲学家2 尝试获取 obj2 和 obj1 时,发现两双筷子都没人使用,直接开始就餐。

总结

什么是死锁?

当多个线程并发后,线程在占有锁资源的情况下还尝试获取别的线程所占有的锁资源。

这样就会操作线程永远获取不到其他锁资源,也不能释放本身锁资源。

从而形成的一种僵局,这就是死锁。

造成死锁的原因是什么?

造成死锁的原因有,互斥、请求和保持、不可抢占、循环等待。(文章中有讲解)

死锁的解决方案

我们可以对多个锁按照顺序进行编号,并约定每个线程在使用多把锁时优先使用最小编号的锁,这样就能保证其他线程再获取小编号锁时,锁资源已得到释放。

到此这篇关于Java多线程中的死锁详解的文章就介绍到这了,更多相关Java多线程死锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Maven打包所有依赖到一个可执行jar中遇到的问题

    Maven打包所有依赖到一个可执行jar中遇到的问题

    这篇文章主要给大家介绍了关于Maven打包所有依赖到一个可执行jar中遇到的问题,将依赖打入jar包,由于maven管理了所有的依赖,所以将项目的代码和依赖打成一个包对它来说是顺理成章的功能,需要的朋友可以参考下
    2023-10-10
  • idea中的Maven导包失败问题解决方案汇总

    idea中的Maven导包失败问题解决方案汇总

    这篇文章主要介绍了idea中的Maven导包失败问题解决总结,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-02-02
  • Springboot居然可以设置动态的Banner(推荐)

    Springboot居然可以设置动态的Banner(推荐)

    这篇文章主要介绍了Springboot居然可以设置动态的Banner,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • java多媒体文件编码 处理工具类代码实例

    java多媒体文件编码 处理工具类代码实例

    这篇文章主要介绍了java多媒体文件编码 处理工具类使用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Java 实现实时监听文件夹是否有新文件增加并上传服务器功能

    Java 实现实时监听文件夹是否有新文件增加并上传服务器功能

    本文中主要陈述一种实时监听文件夹中是否有文件增加的功能,可用于实际文件上传功能的开发。本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友参考下吧
    2019-09-09
  • Java经典设计模式之模板方法模式定义与用法示例

    Java经典设计模式之模板方法模式定义与用法示例

    这篇文章主要介绍了Java经典设计模式之模板方法模式,简单说明了模板方法模式的原理、定义,并结合实例形式分析了java模板方法模式的具体使用方法,需要的朋友可以参考下
    2017-08-08
  • Eclipse 项目出现错误(红色叹号)解决方法

    Eclipse 项目出现错误(红色叹号)解决方法

    这篇文章主要介绍了Eclipse 项目出现错误(红色叹号)解决方法的相关资料,需要的朋友可以参考下
    2017-06-06
  • Java中IO的NIO通道解析

    Java中IO的NIO通道解析

    这篇文章主要介绍了Java中IO的NIO通道解析,NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,需要的朋友可以参考下
    2024-01-01
  • Java 读取外部资源的方法详解及实例代码

    Java 读取外部资源的方法详解及实例代码

    这篇文章主要介绍了Java 读取外部资源的方法详解及实例代码的相关资料,经常有读取外部资源的要求,如配置文件等等需要读取,需要的朋友可以参考下
    2017-03-03
  • HashMap的get()方法的NullPointerException问题

    HashMap的get()方法的NullPointerException问题

    这篇文章主要介绍了HashMap的get()方法的NullPointerException问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09

最新评论