死锁是面试中常考的一个点,也是我们工作中应该尽量避免的BUG。在多线程任务中,死锁是很容易产生的。
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,
它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
简单来说,比如,有两个同事一起工作,他们完成各自的工作都需要用到两个工具,但是使用的顺序又是不同的,比如说A完成工作需要用到工具1,任务完成到某一阶段然后又要用到工具2,而B完成工作所需要的工具顺序与之相反,先用到工具2,然后再用到工具1。但是这两个工具都只有一个实体,不能同时被两个人用,只有在某一人用完该工具并将工具放回到储物柜以后,另一个人才能使用该工具。
这时,就容易产生死锁的问题。比如,当A先拿到了工具1,然后忙活自己的事,在忙活了半小时以后才需要用到工具2,(这时两个工具的状态是:工具1正在被A使用,工具2还留在储物柜),但是这半小时中,B也开始工作了,他看到工具2还留在储物柜中,而他开始工作也正好需要使用到工具2,于是拿起工具2就开始工作(这时两个工具的状态是:工具1正在被A使用,工具2正在被B使用),然后他也开始忙活自己的工作,假设他也需要忙活半小时会使用到工具1。
那么下一步就是A已经做完了第一阶段的工作,需要使用到工具2了,于是就到储物柜那里去取工具2,但是发现这是工具2不在储物柜中,所以就只能等待了,等工具2 被人还回来再继续工作(因为需要用到工具1的任务也没有做完,就不会还回去,于是就拿着工具1等工具2)。
好了这时B也已经做完了第一阶段的工作,需要使用到工具1了,于是就到储物柜那里去取工具1,但是发现这是工具1不在储物柜中,所以就只能等待了,等工具1 被人还回来再继续工作(需要用到工具2的任务也没有做完,就不会还回去,于是就拿着工具2等工具1)。
这时候矛盾就很清晰了,A拿着工具1等工具2,B拿着工具2等工具1。有人就说了,你就不能让他们某一人先腾出手中的工具吗?问题就是还真不能腾出来,因为他们都只能在使用完工具以后才能还回去,但是并不是说我需要工具2的时候,就已经不需要工具1了,而是我需要工具1的同时,拿到工具2.
下面我们结合代码加深理解:
首先看看任务类,这是一套工作流程,只需要将工作的员工姓名和需要先后使用到的工具传入,就能完成这一套流程:
/**
* @author 作者 John L:
* @version 创建时间:May 12, 2018 8:44:06 AM
*/
public class MyRunnable implements Runnable{
private String name; //工作人员名字
private Tool firstTool; //第一阶段需要用到的工具
private Tool secondTool; //第二阶段需要用到的工具
private static Random random = new Random();
public MyRunnable(String name, Tool firstTool, Tool secondTool) {
this.name = name;
this.firstTool = firstTool;
this.secondTool = secondTool;
}
public void run() {
System.out.println(name + ", 我开始工作了");
System.out.println(name +"需要 " + firstTool);
synchronized (firstTool) {
System.out.println(name + "拿到了" + firstTool + " 继续工作");
int firstWorkTime = random.nextInt(10) + 5;
System.out.println(name + " 第一阶段的工作需要 " + firstWorkTime + "秒钟");
try {
TimeUnit.SECONDS.sleep(firstWorkTime);
} catch (InterruptedException e) {
}
System.out.println(name + "完成了第一阶段的工作,这时需要用到" + secondTool);
synchronized (secondTool) {
System.out.println(name + "拿到了" + secondTool + " 继续工作");
System.out.println(name + "使用" + firstTool + "和" + secondTool + "工作");
System.out.println(name + "用完了 " + secondTool);
}
System.out.println(name + "用完了 " + firstTool);
}
}
}
下面我们来看下单线程版本,也就是说只有一个员工会处于工作状态
/**
* @author 作者 John L:
* @version 创建时间:May 12, 2018 9:01:53 AM
*/
public class TestSyn {
public static void main(String[] args) {
Tool tool1 = new Tool("工具1");
Tool tool2 = new Tool("工具2");
new Thread(new MyRunnable("员工A", tool1, tool2)).start();
}
}
看下控制台打印情况:
员工A, 我开始工作了
员工A需要 工具1
员工A拿到了工具1 继续工作
员工A 第一阶段的工作需要 14秒钟
员工A完成了第一阶段的工作,这时需要用到工具2
员工A拿到了工具2 继续工作
员工A使用工具1和工具2工作
员工A用完了 工具2
员工A用完了 工具1
由打印情况可知,这名员工正常地拿到了需要的两个工具完成了工作。
再看看双线程版本,也就是说会有两名员工同时工作。
/**
* @author 作者 John L:
* @version 创建时间:May 12, 2018 9:01:53 AM
*/
public class TestSyn {
public static void main(String[] args) {
Tool tool1 = new Tool("工具1");
Tool tool2 = new Tool("工具2");
new Thread(new MyRunnable("员工A", tool1, tool2)).start();
new Thread(new MyRunnable("员工B", tool2, tool1)).start();
}
}
看下控制台打印情况:
员工A, 我开始工作了
员工B, 我开始工作了
员工A需要 工具1
员工B需要 工具2
员工B拿到了工具2 继续工作
员工A拿到了工具1 继续工作
员工B 第一阶段的工作需要 12秒钟
员工A 第一阶段的工作需要 10秒钟
员工A完成了第一阶段的工作,这时需要用到工具2
员工B完成了第一阶段的工作,这时需要用到工具1
这是两名员工都完成了第一阶段的工作,在需要另一个工具的时候就卡死了。