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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Spring boot zuul 网关「建议收藏」

    Spring boot zuul 网关「建议收藏」Zuul作为微服务系统的网关组件,用于构建边界服务,致力于动态的路由、过滤、监控、弹性伸缩和安全。其中Zuul、Ribbon以及Eureka的结合使用可以实现智能路由和负载均衡的功能,网关将所有的服务的API接口统一聚合,统一对外暴露,外界调用API的接口的时候,不需要知道微服务系统中各服务相关调用的复杂性,保护了内部微服务单元的API接口,网关可以做用户身份认证和权限认证,防止非法请求操作a…

    2022年8月15日
    6
  • CICD构建实验「建议收藏」

    CICD构建实验「建议收藏」CICDCICD是一个可以集部署、拉取、上传等于一体的架构环境,它支持一线进行部署,免去了人工一条条的进行部署环境的工作流程,大大降低了人力手工运维成本和出错率。CICD的搭建需要至少三台服务器,他们分别监管着Harbor(镜像仓库存储),git(开发代码仓库存储),Jenkings(一键化部署)企业级镜像Harbor部署docker镜像级的存储可以储存在dockerhub上,也可以储存在自建本地仓库上,而Harbor属于本地仓库的其中一种,该软件可以提供图形化界面操作,安装简单,且方便查看。

    2022年5月5日
    38
  • vue删除数组中指定元素_如何删除数组中的元素

    vue删除数组中指定元素_如何删除数组中的元素vue删除数组中指定的元素exportdefault{ data(){ return{ mpList:[‘测试一’,’测试二’,’测试三’] } }, methods:{ del(item){ letlistVar=newArray for(leti=0;i<this.mpList.length;i++){ listVar.push(this.mpList) if(this.mpList[i]==item){

    2022年8月10日
    4
  • 新人如何入行3D游戏建模

    新人如何入行3D游戏建模所有行业都是一样的,没有什么容易的,只不过这一行是偏向于技术的,一个有好的建模师月薪10k+是很常见的,这个需要有自己刻苦学习的成果。游戏建模前景在游戏模型行业,你基本不用担心找不到工作,因为游戏模型师人才缺口非常大。举个例子:游戏制作公司的人员配比大多数是这样的:比如100人的三维制作组,可能有60人在做模型贴图,10个人在K动画。只要你保证技能在手,一定是抢手的人才。在几年前游戏建模这个行业不仅仅缺人才,甚至连新手都非常稀缺,那个时候公司愿意招聘实习生,培养他们然后给公司干活,但是工资一定不会给开的很

    2022年5月12日
    49
  • mykit-db-sync之——数据库同步工具正式开源[通俗易懂]

    mykit-db-sync之——数据库同步工具正式开源[通俗易懂]转载请注明出处:https://blog.csdn.net/l1028386804/article/details/82731142重磅消息:数据库同步工具mykit-db-sync正式开源开源地址:https://github.com/sunshinelyz/mykit-db-sync框架简述mykit中分离出的强大的数据库同步工具——mykit-db-sync基于java开发…

    2022年6月16日
    44
  • WinSCP拒绝访问问题

    WinSCP拒绝访问问题WinSCP登录界面的用户名与密码是Ubuntu虚拟机root用户下的用户名与密码

    2025年12月13日
    1

发表回复

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

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