一、概念:

多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进

https://img-blog.csdn.net/20180922173936964?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2hkMTIzNzA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

二、死锁的原因:

1)资源竞争
可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
a)产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
b)产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
2)进程(线程推进顺序非法)
例:有资源a、b,线程1、2。1占用a,2占用b。继续推进1去获取b,2去获取a就会发生死锁

三、死锁的必要条件

(1)互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
(2)不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
(3)请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
(4)循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有,如图1所示。
		资源分配图含圈而系统又不一定有死锁的原因是同类资源数大于1。但若系统中每类资 源都只有一个资源,则资源分配图含圈就变成了系统出现死锁的充分必要条件。

通俗解释:

(1)互斥条件:一个资源每次只能被一个进程使用。独木桥每次只能通过一个人。
(2)不剥夺条件: 进程已获得的资源,在未使用完之前,不能强行剥夺。甲不能强制乙退出桥面,乙也不能强制甲退出桥面。
(3)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。乙不退出桥面,甲也不退出桥面。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。如果乙不退出桥面,甲不能通过,甲不退出桥面,乙不能通过。

四、java案例

/**
 * 一个简单的死锁类
 * t1先运行,这个时候flag==true,先锁定obj1,然后睡眠1秒钟
 * 而t1在睡眠的时候,另一个线程t2启动,flag==false,先锁定obj2,然后也睡眠1秒钟
 * t1睡眠结束后需要锁定obj2才能继续执行,而此时obj2已被t2锁定
 * t2睡眠结束后需要锁定obj1才能继续执行,而此时obj1已被t1锁定
 * t1、t2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
 */
	public class DeadLock implements Runnable{

	private static Object obj1 = new Object();
	private static Object obj2 = new Object();
	private boolean flag;

	public DeadLock(boolean flag){
		this.flag = flag;
	}

	@Override
	public void run(){
		System.out.println(Thread.currentThread().getName() + "运行");

		if(flag){
			synchronized(obj1){
				System.out.println(Thread.currentThread().getName() + "已经锁住obj1");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized(obj2){
					// 执行不到这里
					System.out.println("1秒钟后,"+Thread.currentThread().getName()
							+ "锁住obj2");
				}
			}
		}else{
			synchronized(obj2){
				System.out.println(Thread.currentThread().getName() + "已经锁住obj2");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized(obj1){
					// 执行不到这里
					System.out.println("1秒钟后,"+Thread.currentThread().getName()
							+ "锁住obj1");
				}
			}
		}
	}

}
class TestRun{
	public static void main(String[] args) {
		Thread t1 = new Thread(new DeadLock(true), "线程1");
		Thread t2 = new Thread(new DeadLock(false), "线程2");

		t1.start();
		t2.start();
	}
}
以上代码满足请求与保持条件,t1与t2拿着obj1 obj2都不放,导致死锁

下面是由嵌套引起的死锁

public class SyncThread implements Runnable{

	private Object obj1;
	private Object obj2;

	public SyncThread(Object o1, Object o2){
		this.obj1=o1;
		this.obj2=o2;
	}

	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		synchronized (obj1) {
			System.out.println(name + " acquired lock on "+obj1);
			work();
			synchronized (obj2) {
				System.out.println("After, "+name + " acquired lock on "+obj2);
				work();
			}
			System.out.println(name + " released lock on "+obj2);
		}
		System.out.println(name + " released lock on "+obj1);
		System.out.println(name + " finished execution.");
	}

	private void work() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
public class ThreadDeadTest {

	public static void main(String[] args) throws InterruptedException {
		Object obj1 = new Object();
		Object obj2 = new Object();
		Object obj3 = new Object();

		Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
		Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
		Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");

		t1.start();
		Thread.sleep(1000);
		t2.start();
		Thread.sleep(1000);
		t3.start();

	}
}
在这个例子中,形成了一个锁依赖的环路。以t1为例,它先将第一个对象锁住,但是当它试着向第二个对象获取锁时,它就会进入等待状态,因为第二个对象已经被另一个线程锁住了。
这样以此类推,t1依赖t2锁住的对象obj2,t2依赖t3锁住的对象obj3,而t3依赖t1锁住的对象obj1,从而导致了死锁。在线程引起死锁的过程中,就形成了一个依赖于资源的循环。

五、如何避免死锁

1)加锁顺序(线程按照一定的顺序加锁):
	线程1 锁a,再锁b
	线程2 锁a,再锁b
	避免嵌套锁
2)加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
	wait(),lock() unlock()

六、如何检测死锁

1)jdk bin下自带的JConsole检测死锁 ,图形化界面,选择进程,进去可以查看堆栈信息,线程信息,死锁线程
2)java 中提供了可以检测死锁的工具类ThreadMXBean
public class TestThread {

public static final Object lock1 = new Object();//锁对象1
public static final Object lock2 = new Object();//锁对象2
 //获取ThreadMXBean
public static ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
//测试方法
public static void main(String[] args) throws InterruptedException {
    //启动死锁线程
    new A("A").start();
    new B("B").start();
    //等待一段时间再执行死锁检测
    Thread.sleep(200);
    //获取到所有死锁线程的id
    long[] deadlockedThreads = mbean.findDeadlockedThreads();
    //遍历数组获取所有的死锁线程详细堆栈信息并打印
    for (long pid : deadlockedThreads) {
        //此方法获取不带有堆栈跟踪信息的线程数据
        //hreadInfo threadInfo = mbean.getThreadInfo(pid);
        //第二个参数指定转储多少项堆栈跟踪信息,设置为Integer.MAX_VALUE可以转储所有的堆栈跟踪信息
        ThreadInfo threadInfo = mbean.getThreadInfo(pid,Integer.MAX_VALUE);
        System.out.println(threadInfo);
    }
}

}

用top定位哪个进程对cpu的占用过高
ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
jstack 进程id
可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号(需要将10进制线程编号转换为16进制)