且说 死锁 synchronize

本文通过一个生动的例子介绍了在多线程环境下如何发生死锁,并通过代码实现进一步解释了这一概念。

死锁是面试中常考的一个点,也是我们工作中应该尽量避免的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
这是两名员工都完成了第一阶段的工作,在需要另一个工具的时候就卡死了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值