当前位置: 首页 > backend >正文

什么是线程死锁?如何避免死锁?

1. 什么是线程死锁

线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

在这里插入图片描述
下面的代码模拟了上图的死锁的情况

public class DeadLockDemo {private static Object resource1 = new Object();//资源 1private static Object resource2 = new Object();//资源 2public static void main(String[] args) {new Thread(() -> {synchronized (resource1) {System.out.println(Thread.currentThread() + "get resource1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + "waiting get resource2");synchronized (resource2) {System.out.println(Thread.currentThread() + "get resource2");}}}, "线程 1").start();new Thread(() -> {synchronized (resource2) {System.out.println(Thread.currentThread() + "get resource2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + "waiting get resource1");synchronized (resource1) {System.out.println(Thread.currentThread() + "get resource1");}}}, "线程 2").start();}
}

结果:

Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1

结果说明:

线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过Thread.sleep(1000);让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。
 

2. 产生死锁的必要条件

学过操作系统的朋友都知道产生死锁必须具备以下四个条件:

  1. 互斥条件:该资源任意一个时刻只由一个线程占用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

 

3. 如何避免线程死锁?

上面说了产生死锁的四个必要条件,为了避免死锁,只需要破坏产生死锁的四个条件中的其中一个就可以了。现在我们来挨个分析一下:

  1. 破坏互斥条件 :这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
  2. 破坏请求与保持条件 :一次性申请所有的资源。
  3. 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  4. 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

我们对上面的代码进行修改,将线程 2 的代码修改成下面这样就不会产生死锁了。

 new Thread(() -> {synchronized (resource1) {System.out.println(Thread.currentThread() + "get resource1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + "waiting get resource2");synchronized (resource2) {System.out.println(Thread.currentThread() + "get resource2");}}}, "线程 2").start();

结果:

Thread[线程 1,5,main]get resource1
Thread[线程 1,5,main]waiting get resource2
Thread[线程 1,5,main]get resource2
Thread[线程 2,5,main]get resource1
Thread[线程 2,5,main]waiting get resource2
Thread[线程 2,5,main]get resource2Process finished with exit code 0

结果分析:

我们分析一下上面的代码为什么避免了死锁的发生?

线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
 

参考:

https://github.com/Snailclimb/JavaGuide/

http://www.xdnf.cn/news/11522.html

相关文章:

  • 【iOS开发-22】navigationBar导航条和navigationItem设置:基本搞定导航条上的文字和按钮以及各种跳转
  • Metasploit Framework-安全漏洞检测工具使用
  • 常用SQL语句(基础篇)
  • Proteus与Multisim哪款更适合51单片机仿真?
  • 清空StringBuilder的三种方法及效率
  • Debian9.5系统DNS服务器BIND软件配置说明
  • string.empty null
  • Predix总括
  • Yslow的用法以及安装后无法使用
  • 分享75个ASP江湖论坛源码,总有一款适合您
  • DoDataExchange函数,UpdateData(TRUE)和UpdateData(FALSE)的区别
  • ROI 详解
  • Gradle version 2.10 is required. Current version is 2.8.
  • Node.js超详细教程!
  • eXosip sip
  • [SSD固态硬盘保养 1] 电脑优化设置,告别卡顿,享受顺畅 (独门 11 招)_关闭设备上的windows写入高速缓存缓冲区刷新(4)
  • 2024年最全Android开发和调试必备工具-SDK Tools(1),文档开发工程师面试题
  • Android开发者必备工具-常见Android模拟器(MuMu、夜神、蓝叠、逍遥、雷电、Genymotion
  • 徐粉林同志简历
  • 数据分析必备的5个工具,你用过几个?
  • 什么是trunk
  • 客户关系管理包括哪些内容?
  • New Age音乐启蒙与经典选介
  • EXPORT_SYMBOL
  • 3CX的具体介绍
  • (转)如何应用MTCNN和FaceNet模型实现人脸检测及识别
  • 线程同步之ManualResetEvent 和AutoResetEvent
  • 易语言入门知识点
  • Android基础教程(非常详细)从零基础入门到精通,看完这一篇就够了
  • Amazon S3简介