Java 线程与并发研究系列六(死锁)

我们在学习操作系统的时候,一定接触过哲学家就餐的问题,这是由Edsger Dijkstra提出的经典的死锁例子,该例子描述的是,指定

五个哲学家一起就餐,这些哲学家花一部分时间就餐,一部分时间思考,就餐时,总共五把叉子,每人一把,如果需要就餐就必须拿

到左边或右边的叉子,直到手中有两把叉子时才能就餐,否则哲学家就必须等待,直到得到必须的叉子。

下面我们就编写程序来实现这样一个场景

public class Test {

	public static void main(String[] args) {
		ExecutorService exec = Executors.newCachedThreadPool();
		Fork [] array = new Fork[5];
		for (int i = 0; i < 5; i++) {
			array[i] = new Fork();
		}
		
		for (int i = 0; i < array.length; i++) {
			exec.execute(new Philosopher(array[i], array[(i+1)%5],5,i));
		}
		try {
			TimeUnit.SECONDS.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		exec.shutdownNow();
	}
}

class Philosopher implements Runnable{
	Fork forkLeft,forkRight;
	int time;//休息时间
	int num;//哲学家编号
	Random rand = new Random();
	
	public Philosopher(Fork forkLeft,Fork forkRight,int time,int num){
		this.forkLeft = forkLeft;
		this.forkRight = forkRight;
		this.time = time;
		this.num = num;
	}

	@Override
	public void run() {
		try {
			while(!Thread.interrupted()){
				forkLeft.take();
				forkRight.take();
				
				rest();
				
				forkLeft.drop();
				forkRight.drop();
				System.out.println("哲学家"+num+"号已经吃了一次");
			}		
		} catch (InterruptedException e) {
			System.out.println("结束用餐");
		}
	}
	
	public void rest(){
		try {
			TimeUnit.MILLISECONDS.sleep(rand.nextInt(time*250));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class Fork{
	
	private boolean isTake = false;
	
	public synchronized void take()throws InterruptedException{
		while(isTake){
			wait();
		}
		isTake = true;
	}
	
	public synchronized void drop()throws InterruptedException{
		isTake = false;
		notifyAll();
	}
}

上面Fork类表示叉子,Philosopher表示哲学家,哲学家分别从左边和右边的人那里利用take方法获取叉子,吃完后在调用drop方法进行释放。

上面的例子,我只让他运行了十秒,在这个时间段你可能看不到死锁,如果你永久的运行下面可能就会产生死锁。为什么会产生死锁呢?从

上面的例子中我们可以看到,哲学家在用餐的时候都是先获取左边的叉子,然后获取右边的叉子,如果获取到了左边的叉子,在获取右边的叉子

时,右边的叉子已经被人获取了,那么他就处于等待状态,同时没有释放之前获取的左边的叉子。假如他们都处于这样一个场景:0号哲学家获取

了左边4号哲学家的叉子,1号获取0号的,2号获取1号的,3号获取2号的,4号获取3号的,只要遇到这样的场景,那么就永远不会有人同时拿到

2把叉子,所有的人都会永久的等待下去,这样就造成了死锁。

造成死锁的几个必要条件:

1、互斥条件,至少有一个条件是不能共享的,这里的叉子就不能同时被两个人使用。

2、循环等待,一个任务等待另一个任务所持有的资源,而另一个任务又在等待前者所持有的资源。这里如果哲学家都相互持有另外一个哲学家的

叉子后,就会相互等待另一方释放叉子,那么就会造成循环等待的场景。

3、非抢占,任何任务所持有的资源,除非自己释放掉,否则其他任务不能抢占。

4、持有等待,至少有一个任务必须持有一个资源,并且正在等待获取一个当前被其他任务持有的资源。这里哲学家获取到了左边的人的叉子,如果

右边的人叉子已经被其他的人获取,那么这个哲学家就会持有左边的人的叉子,并且等待其他人释放右边的人的叉子。

那么怎样来预防上面这个可能会产生死锁的例子呢?只要破坏上面4个条件中的一个就会打破死锁的局面,在这里我们打破第二个条件就会消除死锁

只要其中有一人先获取右边再获取左边的叉子就会打破这种局面,这样就会使得桌子上至少能够存在一对叉子可以使用,这一死锁便解除了。

你可能感兴趣的:(Java 线程与并发研究系列六(死锁))