Java 线程死锁及如何避免死锁介绍

Java 线程死锁及如何避免死锁介绍死锁是指两个或两个以上的线程在执行过程中,**因争夺资源而造成的互相等待**的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去,

大家好,又见面了,我是你们的朋友全栈君。

1. 什么是线程死锁

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去,如下图所示
在这里插入图片描述
在上图中,线程 A 已经持有了资源 2,它同时还想申请资源 1,线程 B 已经持有了资源 1,它同时还想申请资源 2,所以线程 1 和线程 2 就因为相互等待对方已经持有的资源,而进入了死锁状态。

2. 死锁产生的原因

那么为什么会产生死锁呢? 主要是由以下四个因素造成的:

  • 互斥条件:指线程对以获取到的资源进行排他性使用,即该资源同时只由一个线程占用。如果此时还有其他线程请求获取该资源,则请求者只能只能等待,直到占有资源的线程释放该资源。
  • 不可被剥夺条件:指线程获取到的资源在自己使用完之前不能被其他线程抢占,只有在自己使用完毕后才由自己释放该资源。
  • 请求并持有条件:值一个线程已经持有了至少一个资源,但又提出了新的资源请求,而新资源已被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己已经获取的资源。
  • 环路等待条件:指在发生死锁时,必然存在一个(线程 — 资源)的环形链,即线程集合 {T0,T1,T2,…,Tn} 中的 T0 正在等待一个 T1 占用的资源,T1正在等待 T2 占用的资源,······Tn 正在等待已被 T0 占用的资源。

下面用一个例子来说明线程死锁:

public class ThreadDemo_线程死锁 { 
   
    public static void main(String[] args) { 
   
        Object lockA = new Object();
        Object lockB = new Object();
        Thread t1 = new Thread(new Runnable() { 
   
            @Override
            public void run() { 
   
                synchronized (lockA) { 
   
                    System.out.println("线程1 获得锁A");
                    try { 
   
                        Thread.sleep(1000);
                    } catch (InterruptedException e) { 
   
                        e.printStackTrace();
                    }
                    System.out.println("线程1 等待锁B");
                    synchronized (lockB) { 
   
                        System.out.println("线程1 获得锁B");
                    }
                }
            }
        });
        t1.start();
        Thread t2 = new Thread(new Runnable() { 
   
            @Override
            public void run() { 
   
                synchronized (lockB) { 
   
                    System.out.println("线程2 获得锁B");
                    try { 
   
                        Thread.sleep(1000);
                    } catch (InterruptedException e) { 
   
                        e.printStackTrace();
                    }
                    System.out.println("线程2 等待锁A");
                    synchronized (lockA) { 
   
                        System.out.println("线程2 获得锁A");
                    }
                }
            }
        });
        t2.start();
    }
}

通过C:\Program Files\Java\jdk1.8.0_192\bin下的 VisualVM 就可以观察到我们启动的线程检测到了死锁,导致无法继续运行也无法结束进程,进入死锁状态。
在这里插入图片描述
输出如下结果:
在这里插入图片描述

分析代码结果:Thread-0 是线程1 ,Thread-1 是线程 2,代码首先创建了两个资源,并创建了两个线程。从结果输出可以看出,线程调度器先调度了线程 1,也就是把 CPU 资源分配给了线程 A,线程 A 使用 synchronized(lockA) 方法获得到了 lockA 的监视器锁,然后调用 sleep 函数休眠 1s,休眠 1s 是为了保证线程 1 在获取 lockB 对应的锁前让 线程 2 抢占到 CPU,获取到资源 lockB 对象的监视器锁资源,然后调用 sleep 函数休眠 1s。

好了,到了这里线程 1 获取到了 lockA 资源,线程 2 获取到了 lockB 资源。线程 1 休眠结束后会企图获取 lockB 资源,而 lockB 资源被线程 2 所持有,所以线程 1 会阻塞而等待。而同时线程 2 休眠结束后会企图获取 lockA 资源,而 lockA 资源已经被线程 1 所持有,**所以线程 1 和线程 2 就陷入了相互等待的状态,也就产生了死锁。**下面谈谈本例是如何满足死锁的四个条件的。

首先,lockA 和 lockB 都是互斥资源,当线程 1 调用了 synchronized(lockA) 方法获得到 lockA 上的监视器并释放前,线程 2 再调用 synchronized(lockA) 方法尝试获取该资源会被阻塞,只有线程 1 主动释放该锁,线程 2 才能获得,这满足了资源互斥条件

线程 1 首先通过 synchronized(lockA) 方法获取到 lockA 上的监视器锁资源,然后通过 synchronized(lockB) 方法等待获取 lockB 上的监视器锁资源,这就构成了请求并持有条件

线程 1 在获取 lockA 上的监视器锁资源后,该线程不会被线程 2 掠夺走,只有线程 1 自己主动释放 lockA 资源时,他才会放弃对该资源的持有权,这构成了资源不可剥夺条件

线程 1 持有 lockA 资源并等待获取 lockB 资源,而线程 2 只有 lockB 资源并等待获取 lockA 资源,这构成了环路等待条件。所以线程 1 和线程 2 就进入了死锁状态。

3. 如何避免线程死锁。

要想避免死锁,只需要破坏掉至少一个构造死锁的必要条件即可,而在操作系统中,互斥条件和不可剥夺条件是系统规定的,这也没办法人为更改,而且这两个条件很明显是一个标准的程序应该所具备的特性。所以目前只有请求并持有和环路等待条件是可以被破坏的。

造成死锁的原因其实和申请资源的顺序有很大关系 使用资源申请的有序性原则就可以避免死锁,那么什么是资源申请的有序性呢?我们对上面线程 2 的代码进行如下修改。

// 创建线程 2
 Thread t2 = new Thread(new Runnable() { 
   
            @Override
            public void run() { 
   
                synchronized (lockA) { 
   
                    System.out.println("线程2 获得锁A");
                    try { 
   
                        Thread.sleep(1000);
                    } catch (InterruptedException e) { 
   
                        e.printStackTrace();
                    }
                    System.out.println("线程2 等待锁B");
                    synchronized (lockB) { 
   
                        System.out.println("线程2 获得锁B");
                    }
                }
            }
        });
        t2.start();
    }

输出结果如下:
在这里插入图片描述
如上代码让在线程 2 中获取资源的顺序和在线程 1 中获取资源的顺序保持一致,其实资源分配有序性就是指,假如线程 1 和线程 2 都需要资源 1, 2, 3, … . , ,对资源进行排序,线程 1 和线程 2 有在获取了资源 n-1 时才能去获取资源 n

我们可以简单分析一下为何资源的有序分配会避免死锁,比如上面的代码,假如线程1 和线程 2 同时执行到了 synchronized(lockA),只有1个线程可以获取到 lockA 上的监视器锁,假如线程 1 获取到了,那么线程 2 就会被阻塞而不会再去获取资源 B,线程 1 获取 lockA 的监视器锁后会去申请 lockB 的监视器锁资源,这时候线程 1 是可以获取到的,线程 1 获取到 lockB 资源并使用后会放弃对资源 lockB 的持有,然后再释放对 lockA 的持有,释放 lockA 后线程 2 才会被从阻塞状态变为激活状态。所以资源的有序性破坏了资源的请求并持有条件和环路等待条件,因此避免了死锁。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/158662.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Java中的构造方法[通俗易懂]

    Java中的构造方法[通俗易懂]简述前言:【08-10】构造方法   定义:就是类构造对象时调用的方法,主要用来实例化对象。构造方法分为无参构造方法、有参构造方法。   概念:构方法是一种特殊的“成员方法”     1,构造方法作用:(1)构造出来一个类的实例(2)对构造出来个一个类的实例(对象)初始化     2,构造方法的名字必须与定义他的类名完全相同,没有返回类型,甚至连void也没有 …

    2022年7月8日
    23
  • 模糊数学学习笔记

    模糊数学学习笔记一 什么是模糊数学为了精确地描述复杂的现实对象 各类数学分支不断的产生和发展 迄今为止 处理现实对象的数学模型可分为三大类 1 确定性数学模型 这类模型的背景对象具有确定性或固定性 对象间具有必然的关系 2 随机性数学模型 这类模型的背景对象具有或然性或随机性 3 模糊性数学模型 这类模型的背景对象及其关系具有模糊性 前两类模型的共同特点是所描述的事物本身的含义是确定的 他们赖

    2025年7月26日
    4
  • Qt之msvc-version.conf loaded but QMAKE_MSC_VER isn‘t set[通俗易懂]

    Qt之msvc-version.conf loaded but QMAKE_MSC_VER isn‘t set[通俗易懂]最近用Qt5.10.0VS2015新建一个工程,构建时报如下错误:msvc-version.confloadedbutQMAKE_MSC_VERisn’tset解决方法:打开文件D:\Qt\Qt5.10.0\5.10.0\msvc2015\mkspecs\common\msvc-version.conf在其中添加版本QMAKE_MSC_VER=1900,如下图所

    2022年5月19日
    68
  • 公网IP和内网IP如何分辨?

    公网IP和内网IP如何分辨?公网ip和内网ip之间如何分辨,公网ip和内网ip之间有什么区别?很多人都知道根据网络使用的范围不同又分为公有网络和私有网络。公有网络就是指处于公有网络的电脑的IP是“互联网”中能够识别到的地址;而私有网络指公有网络的机器不能识别到的机器。本文主要给大家介绍公网ip和内网ip的相关知识。

    2022年4月29日
    59
  • 毕设不会做怎么办_毕设网

    毕设不会做怎么办_毕设网身边很多从事办公室的白领,经常会听他们说:腰椎不行了,有点难受,要不就颈椎也不舒服,这些常见的现象不可忽视,它会对人们后面的生活产生很多负面的影响,所以我们想到能不能有这么一个设备,它会定期提醒人们不要坐太久。其实久坐提醒不是一个新鲜事,市面上也有许许多多关于久坐提醒的工具神器,但是,今天我们HaaS团队就手把手教长期在办公室久坐着的你亲手打造一款属于自己的久坐提醒设备,当你长时间在工位上坐着,它会通过钉钉提醒你,让你一段时间去活动一下筋骨,走动走动,这样让我们上班的同时身体也变得更健康。1、…

    2022年10月1日
    3
  • CSS,font-family,好看常用的中文字体

    CSS,font-family,好看常用的中文字体

    2021年9月20日
    325

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号