死锁产生的四个必要条件:
1.互斥条件:线程对于所分配到的资源具有排它性,即一个资源只能被一个线程占用,直到被该线程释放
2.请求和保持条件:一个线程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3.不剥夺条件:任何一个资源在没被该线程释放之前,任何其他线程都无法对他剥夺占用
4.循环等待条件:当发生死锁时,所等待的线程必定会形成一个环路(类似于死循环),造成永久阻塞。
处理死锁的方法
(1)预防死锁
去破坏产生死锁的四个必要条件中的一个或几个来预防死锁的产生。
(2)避免死锁
在资源的动态分配过程中,用某种方法防止系统进入不安全状态,避免死锁的发生。
(3)检测死锁
通过检测机构检测出死锁的发生,然后采取适当的措施。
(4) 解除死锁
当检测到死锁已经发生时,撤销一些线程,回收他们的资源,将他们分配给其他需要的线程,使其继续运行。
预防死锁方法:
1. 破坏互斥条件:
对于可以被多个进程或线程共享的资源,不再设置互斥锁。
2. 破坏请求和保持条件:
在申请资源前,先释放当前持有的资源。
3. 破坏不剥夺条件:
如果有必要,操作系统可以暂停某个进程或线程,并释放其持有的资源。
4. 破坏环路等待条件:
通过定义资源的顺序,避免进程或线程之间的循环等待。
避免死锁方法:
- 加锁顺序
- 加锁时限
- 死锁检测
加锁顺序
当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。
如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。
加锁时限
另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行(译者注:加锁超时后可以先继续运行干点其它事情,再回头来重复之前加锁的逻辑)。
死锁检测方法:
一种常用的死锁检测算法是银行家算法。该算法基于资源分配的原则来判断系统是否会发生死锁。当资源请求符合银行家算法的规定时,系统不会出现死锁。但如果资源请求不符合银行家算法的规定,则可能会出现死锁。
除此之外,还可以使用其他的死锁检测算法,如等待图算法、有向图算法等。
银行家算法是一种资源分配算法,旨在确保在同时满足可分配资源需求和系统安全性的情况下,分配资源给多个进程。该算法的核心思想是将系统中的资源分为多个类别,并且在分配资源时使用贪心的策略,确保可以满足所有进程的需求,并且不会造成死锁或资源耗尽等问题。
1、Jstack命令
jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
2、JConsole工具
Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。
为避免死锁,可以采取一下措施:
- 避免嵌套锁:尽量避免在持有一个锁的时候去请求另一个锁。
- 减小锁的粒度:尽量将锁的粒度缩小到最小,只锁定必要的代码段。
- 使用超时机制:在请求锁的时候设置一个超时时间,如果在指定时间内无法获取到锁,则放弃。
- 避免循环等待:尽量避免出现多个线程循环等待对方的资源的情况。
- 使用死锁检测:在程序运行时,定期检测是否存在死锁情况,并采取相应的措施加以解决。