在工作中我们可能遇到死锁的问题,但是这个问题是很难排查的,我们平时通过日志来查看项目中的任务产生的问题,但是对于某一些死锁问题的是很难排查出来的。所以本博文将详细的讲述项目的死锁的产出设死锁的定位与分析方法。
死锁的定义死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,投无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
系统中的资源可以分为两类:
- 可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
- 另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
进程间推进顺序非法- 若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
- 例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链
- 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)。
- 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)。
- 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)。
- 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)。
- 首先为每个进程和每个资源指定一个唯一的号码;
- 然后建立资源分配表和进程等待表。
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
- 以确定的顺序获得锁,如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。
- 超时放弃:当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。
package com.zhuangxiaoyan.jdk.juc.JucLock;
import java.util.concurrent.TimeUnit;
/**
* @Classname deadLockDemo
* @Description TODO
* @Date 2021/11/28 7:56
* @Created by xjl
*/
class HoldLockThread implements Runnable {
private String lockA;
private String lockB;
public HoldLockThread(String lockA, String lockB) {
this.lockA=lockA;
this.lockB=lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"\t"+"自己持有"+lockA+"\t 尝试获得"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"\t 自己持有"+lockB+"\t 尝试获得:"+lockA);
}
}
}
}
public class deadLockDemo {
public static void main(String[] args) {
String lockA="lockA";
String lockB="lockB";
new Thread(new HoldLockThread(lockA,lockB),"ThreadAAA").start();
new Thread(new HoldLockThread(lockB,lockA),"ThreadBBB").start();
}
}
死锁问题定位
linux使用jmap,jstat,jstack
jmap: 这个命令是用来查看当前系统中jvm进程 heap dump的情况,包括对象的数量,对象所占内存的大小。使用 jmap -heap PID 生成java堆的详细信息。使用 jmap -histo PID 生成java堆中对象的相关信息,包含数量以及占用的空间大小。
jstat:主要是用来监控 heap size 和 jvm垃圾回收情况,尤其是gc情况的监控,如果老年代发生full gc,那么很可能会导致内存泄漏的可能性。
jstack:先使用 top 查看系统中消耗cpu比较多的进程,然后使用 top -p PID -H来查看当前进程中比较消耗cpu的线程,拿到消耗cpu比较高的线程pid,先转换成16进制的,最后使用jstack pid|grep 16进制的线程id。
死锁面试题(什么是死锁,产生死锁的原因及必要条件)_hd12370的博客-CSDN博客_死锁