Java之路:死锁

在没有讲解死锁的概念之前,我们先从一个生活中的案例来体会一下:

假设有甲乙两个人在就餐,每个人就餐必须同时拥有一把餐刀和一把叉子。但目前餐具不足,统共只有一把餐刀和一把叉子。现在,甲拿到了一把餐刀,乙拿到了一把叉子,他们都无法吃到饭。于是,就有了下面的对话:
甲:“乙你先给我叉子,我再给你餐刀!”
乙:“甲你先给我餐刀,我才给你叉子!”……

如果甲乙双方都不让步,那么局面就会一直僵持下去,他们只能一直等下去,只到等死——这就是“死锁”在生活中的影子,如下图所示:
在这里插入图片描述

如果将上面案例的人数扩展到5人,就是著名计算机科学家迪科斯彻(Dijkstra) 1965年提出的经典的同步问题—— “哲学家进餐(Dining philosophers problem)”

在操作系统中,计算资源不足是常态。通常我们使用共享的方法来解决资源不足的问题。

例如,操作系统中有很多执行中的程序——进程(或线程),都想使用打印机,在一般情况下,我们不会为每个执行中的程序配备一台打印机,而是让它们共享一台打印机。有些设备(如打印机)是独占设备,即一个进程(或线程)在使用该设备时,其他进程(或线程)是不能使用的

为了达到这个目的,操作系统使用了“锁”的概念,来保证对某个设备的独占访问。

独占设备也是可以实现资源共享的,但它们的共享是通过 “我完全不用时,你再用” 来实现的。

进程或线程在执行过程中,通常需要不止一种计算资源,一旦有多个进程或线程已经分配部分独占设备,同时又想申请其他进程或线程占据的独占设备,那么就有可能发生死锁。 如果有一组进程(或线程),其中的每一个都无限等待被该组进程(或线程)中另一进程(或线程)所占有的资源,就会产生无限期地僵持下去的局面,这种现象称为死锁。

具体到Java中的多线程编程,最常见的死锁形式是当线程1已经占据资源R1,并持有资源R1上的锁,而且正在等待资源R2的开锁;而线程2已经占据资源R2,并持有资源R2上的锁,却正在等待资源R1的开锁。如果两个线程不释放自己占据的资源锁,而还申请对方资源的上的锁,申请不到时就等待,那么它们只能永远等待下去。

要预防死锁有多种方法,其中一种就是利用有序资源分配策略,把系统的所有资源排列成一个顺序。

例如,系统若共有n个线程,共有m个资源,用Ri(1≤i≤m)表示第i个资源,于是这m个资源是:如果规定线程i在占用资源Ri时候不得再申请Rj(i>j),即在申请多项资源时,这种策略要求线程申请资源必须按以编号上升的次序依次申请——这样做,在本质上破坏了死锁的必要条件——循环等待。

回到刚才的例子,如果按照有序资源分配的策略,线程1必须先得到资源R1,然后再申请资源R2。而线程2也必须先得到资源R1,才能申请资源R2,即使它需要先使用资源R2,在线程2没有得到R1之前,它被禁止申请资源R2。虽然一开始线程2可能因为等待R1且不占据R2而浪费一些时间,但是这种等待和“奉献精神”换来了避免死锁的发生。

**在真实程序中,死锁是较难发现的。**在下面的例子模拟了死锁的发生:

package com.xy.test3;

public class DeadLockDemo {
	static String knife = "餐刀", fork = "叉子";
	static class A extends Thread {
		public void run() {
			synchronized(knife) {
				System.out.println("甲拿起了" + knife + ", 等待" + fork + "...");
				try {
					Thread.sleep(100);
				}
				catch(InterruptedException e) {
					synchronized(fork) {	// 由于死锁,这段代码永远不会执行
						System.out.println("甲双拿起了" + fork);
					}
				}
			}
		}
	}
	static class B extends Thread {
		public void run() {
			synchronized(fork) {
				System.out.println("乙拿起了" + fork + ", 等待" + knife + "...");
				try {
					Thread.sleep(100);
				}
				catch(InterruptedException e) {
					synchronized(fork) {	// 由于死锁,这段代码永远不会执行
						System.out.println("乙双拿起了" + knife);
					}
				}
			}
		}
	}
	static class Demo extends Thread {
		public Demo() {
			this.setDaemon(true); 	// 设置守护线程
		}
		public void run() {
			while(true) {
				try {
					Thread.sleep(1000);
				}
				catch(InterruptedException e) {
					System.out.println("守护线程:程序运行中...");
				}
			}
		}
	}
	public static void main(String[] args) {
		new A().start();
		new B().start();
		new Demo().start();
	}
}

【结果】
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值