欢迎大家关注我的公众号【老周聊架构】,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。

一、导致线程死锁的原因

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,而该资源又被其他线程锁定,从而导致每一个线程都得等其它线程释放其锁定的资源,造成了所有线程都无法正常结束。这是从网上其他文档看到的死锁产生的四个必要条件:

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用。
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。下面用java代码来模拟一下死锁的产生。

模拟两个资源:

class ThreadResource {public static Object resource1 = new Object();public static Object resource2 = new Object();
}

模拟线程1占用资源1并申请获得资源2的锁:

class Thread1 implements Runnable {@Overridepublic void run() {try {System.out.println("Thread1 is running");synchronized (ThreadResource.resource1) {System.out.println("Thread1 lock resource1");Thread.sleep(2000);//休眠2s等待线程2锁定资源2synchronized (ThreadResource.resource2) {System.out.println("Thread1 lock resource2");}System.out.println("Thread1 release resource2");}System.out.println("Thread1 release resource1");} catch (Exception e) {System.out.println(e.getMessage());}System.out.println("Thread1 is stop");}
}

模拟线程2占用资源2并申请获得资源1的锁:

class Thread2 implements Runnable {@Overridepublic void run() {try {System.out.println("Thread2 is running");synchronized (ThreadResource.resource2) {System.out.println("Thread2 lock resource2");Thread.sleep(2000);//休眠2s等待线程1锁定资源1synchronized (ThreadResource.resource1) {System.out.println("Thread2 lock resource1");}System.out.println("Thread2 release resource1");}System.out.println("Thread2 release resource2");} catch (Exception e) {System.out.println(e.getMessage());}System.out.println("Thread2 is stop");}
}

同时运行俩个线程:

public class ThreadTest {public static void main(String[] args) {new Thread(new Thread1()).start();new Thread(new Thread2()).start();}}

完整代码:

package com.concurrent.deadLock;/*** @author riemann* @date 2019/08/14 22:44*/
public class ThreadTest {public static void main(String[] args) {new Thread(new Thread1()).start();new Thread(new Thread2()).start();}}class ThreadResource {public static Object resource1 = new Object();public static Object resource2 = new Object();
}class Thread1 implements Runnable {@Overridepublic void run() {try {System.out.println("Thread1 is running");synchronized (ThreadResource.resource1) {System.out.println("Thread1 lock resource1");Thread.sleep(2000);//休眠2s等待线程2锁定资源2synchronized (ThreadResource.resource2) {System.out.println("Thread1 lock resource2");}System.out.println("Thread1 release resource2");}System.out.println("Thread1 release resource1");} catch (Exception e) {System.out.println(e.getMessage());}System.out.println("Thread1 is stop");}
}class Thread2 implements Runnable {@Overridepublic void run() {try {System.out.println("Thread2 is running");synchronized (ThreadResource.resource2) {System.out.println("Thread2 lock resource2");Thread.sleep(2000);//休眠2s等待线程1锁定资源1synchronized (ThreadResource.resource1) {System.out.println("Thread2 lock resource1");}System.out.println("Thread2 release resource1");}System.out.println("Thread2 release resource2");} catch (Exception e) {System.out.println(e.getMessage());}System.out.println("Thread2 is stop");}
}

输出结果:

Thread1 is running
Thread1 lock resource1
Thread2 is running
Thread2 lock resource2

并且程序一直无法结束。这就是由于线程1占用了资源1,此时线程2已经占用资源2,。这个时候线程1想要使用资源2,线程2想要使用资源1。两个线程都无法让步,导致程序死锁。

二、怎么解除线程死锁

由上面的例子可以看出当线程在同步某个对象里,再去锁定另外一个对象的话,就和容易发生死锁的情况。最好是线程每次只锁定一个对象并且在锁定该对象的过程中不再去锁定其他的对象,这样就不会导致死锁了。比如将以上的线程改成下面这种写法就可以避免死锁:

public void run()
{try{System.out.println("Thread1 is running");synchronized (ThreadResource.resource1){System.out.println("Thread1 lock resource1");Thread.sleep(2000);//休眠2s等待线程2锁定资源2}System.out.println("Thread1 release resource1");synchronized (ThreadResource.resource2){System.out.println("Thread1 lock resource2");}System.out.println("Thread1 release resource2");}catch (Exception e){System.out.println(e.getMessage());}System.out.println("Thread1 is stop");
}

但是有的时候业务需要同时去锁定两个对象,比如转账业务:A给B转账,需要同时锁定A、B两个账户。如果A、B相互同时转账的话就会出现死锁的情况。这时可以定义一个规则:锁定账户先后的规则。根据账户的某一个属性(比如id或者hasCode),判断锁定的先后。即每一次转账业务都是先锁定A再锁定B(或者先锁定B在锁定A),这样也不会导致死锁的发生。比如按照上面的例子,需要同时锁定两个资源,可以根据资源的hashcode值大小来判断先后锁定顺序。可以这样改造线程:

class Thread3 implements Runnable {@Overridepublic void run() {try {System.out.println("Thread is running");if (ThreadResource.resource1.hashCode() > ThreadResource.resource2.hashCode()) {//先锁定resource1synchronized (ThreadResource.resource1) {System.out.println("Thread lock resource1");Thread.sleep(2000);synchronized (ThreadResource.resource2){System.out.println("Thread lock resource2");}System.out.println("Thread release resource2");}System.out.println("Thread release resource1");} else {//先锁定resource2synchronized (ThreadResource.resource2){System.out.println("Thread lock resource2");Thread.sleep(2000);synchronized (ThreadResource.resource1){System.out.println("Thread lock resource1");}System.out.println("Thread release resource1");}System.out.println("Thread release resource2");}} catch (Exception e) {System.out.println(e.getMessage());}System.out.println("Thread1 is stop");}
}

输出结果:

Thread is running
Thread lock resource2
Thread lock resource1
Thread release resource1
Thread release resource2
Thread1 is stop

三、如何避免死锁

在有些情况下死锁是可以避免的。三种用于避免死锁的技术:

1、加锁顺序
2、加锁时限
3、死锁检测

加锁顺序

当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就容易发生。

按照顺序加锁是一种有效的死锁预防机制。但是,这种方式需要事先知道所有可能会用到的锁,但总有些时候是无法预知的。

加锁时限

当一个线程在尝试获取锁的过程中超过了这个时限则该线程应该放弃对该锁进行请求。

若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行。

需要注意的是,由于存在锁的超时,所以我们不能认为这种场景就一定是出现了死锁。也可能是因为获得了锁的线程(导致其它线程超时)需要很长的时间去完成它的任务。

此外,如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。如果只有两个线程,并且重试的超时时间设定为0到500毫秒之间,这种现象可能不会发生,但是如果是10个或20个线程情况就不同了。因为这些线程等待相等的重试时间的概率就高的多(或者非常接近以至于会出现问题)。

死锁检测

死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。

每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。

当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。

那么当检测出死锁时,这些线程该做些什么呢?

一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁(编者注:原因同超时类似,不能从根本上减轻竞争)。

一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。

总结:死锁常见于,线程在锁定对象还没释放时,又需要锁定另一个对象,并且此时该对象可能被另一个线程锁定。这种时候很容易导致死锁。因此在开发时需要慎重使用锁,尤其是需要注意尽量不要在锁里又加锁。

导致线程死锁的原因?怎么解除线程死锁相关推荐

  1. 产生死锁的原因以及快速定位死锁的位置

    目录 产生死锁的四大因素 解决死锁 快速定位产生死锁的进程 死锁:并发下线程因为互相等待对方资源,导致的阻塞现象 产生死锁的四大因素 1.互斥:共享资源只能被一个线程占用(互斥锁) 2.占有且等待:线 ...

  2. 调试JDK源码-Hashtable实现原理以及线程安全的原因

    调试JDK源码-一步一步看HashMap怎么Hash和扩容 调试JDK源码-ConcurrentHashMap实现原理 调试JDK源码-HashSet实现原理 调试JDK源码-调试JDK源码-Hash ...

  3. 【转载】计算机操作系统出现死锁的原因

    目录 1. 死锁的概念以及产生死锁的原因 1.1 死锁的定义 1.2 死锁产生的原因 1) 系统资源的竞争 2) 进程推进顺序非法 3) 死锁产生的必要条件 2. 死锁的处理策略 预防死锁 避免死锁 ...

  4. java死锁的产生原因,操作系统产生死锁的原因和处理策略

    作者:Cyapirear 素材来源:华为开发者论坛 产生死锁的原因 当进程需要以独占的方式访问资源时,可能会发生死锁(Deadlock).死锁是指两个或以上进程因竞争临界资源而造成的一种僵局,即一个进 ...

  5. python线程池原理_Django异步任务线程池实现原理

    这篇文章主要介绍了Django异步任务线程池实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 当数据库数据量很大时(百万级),许多批量数据修改 ...

  6. 多线程编程:线程死锁的原因以及解决方法

    多线程编程:线程死锁的原因以及解决方法 关于线程死锁这个问题相信程序员在编写多线程程序时会经常遇到的一个经典问题,这种情况往往出现在多个线程同时对临界资源访问时所产生的. 属于临界资源的硬件有打印机. ...

  7. python线程死锁的原因,浅谈Python线程的同步互斥与死锁

    线程间通信方法 1. 通信方法 线程间使用全局变量进行通信 2. 共享资源争夺 共享资源:多个进程或者线程都可以操作的资源称为共享资源.对共享资源的操作代码段称为临界区. 影响 : 对共享资源的无序操 ...

  8. 高并发编程-线程通信_使用wait和notify进行线程间的通信2_多生产者多消费者导致程序假死原因分析

    文章目录 概述 jstack或者可视化工具检测是否死锁(没有) 原因分析 概述 高并发编程-线程通信_使用wait和notify进行线程间的通信 - 遗留问题 我们看到了 应用卡住了 .... 怀疑是 ...

  9. 对话框中WaitForSingleObject等待线程退出导致程序阻塞的原因及解决

    今天在调试程序中发现了程序中出现的一个问题,具体如下: 在对话框中新建一个线程worker thread,当用户点击cancel时,通知该线程函数退出,同时用WaitForSingleObject等待 ...

最新文章

  1. 并查集(边带权,拓展域)
  2. substr vs substring
  3. python构造icmp数据包_如何在python中构造ICMP数据包
  4. Win7实用技巧之五库功能妙用
  5. oracle 回车、换行符
  6. maven项目修改java编译版本的方式
  7. Leetcode--130. 被围绕的区域(java)
  8. 企业级 SpringBoot 教程 (八)springboot整合mongodb
  9. UPUPW数据库密码的修改
  10. 聚焦数据安全管理——安踏信息安全管理体系实践
  11. 【深度】专访华宝基金首席信息官李孟恒:搭建数据驱动引擎,开创投研一体新格局
  12. android基础面试题(一)
  13. BOOST 升压电路调试笔记
  14. 把计算机怎么连接手机的网络助手在哪里,怎么将手机网络通过USB共享给电脑
  15. 基于蒙特卡洛模拟的大规模电动车充电模型
  16. php 微信代扣开发步骤,PHP实现微信支付(jsapi支付)流程步骤详解
  17. 小白聊智慧制造之二:智能制造的体系架构
  18. swift野梦抄袭 taylor_如何看待蔡健雅新歌《半途》被指抄袭 Taylor Swift 的《Safe Sound》?...
  19. 2020启智开发者大会精彩来袭,这些公开课不容错过!
  20. Java计算机毕业设计水果购物商城源码+系统+数据库+lw文档

热门文章

  1. 苹果5s能开机显示无服务器,iphone5s无法开机 iphone5s不能开机解决方法步骤
  2. 八一学院计算机应用基础怎么考,国家开放大学学习指南形考作业参考答案
  3. 神武服务器物品,《神武4》装备鉴赏 欣赏一下这些神兵利器
  4. 【typeof的用法】
  5. 乐鑫Esp32学习之旅29 安信可 ESP32-Cam 摄像头开发板二次开发 C SDK编程,实现本地视频流监控。(附带源码)
  6. n918st能刷Android5吗?,中兴N918st(V5S 双4G版)一键救砖教程,轻松刷回官方系统
  7. 数值优化学习笔记(一)凸集、凸函数信息
  8. visio 2010
  9. 国产编程语言「木兰」实为 Python 套壳,官方致歉!
  10. 笔记 | 这就是软件工程师(一)