死锁—哲学家吃饭问题
2014-09-11 21:27
288 查看
由于线程能被阻塞,更由于synchronized方法能阻止其它线程访问本对象,因此有可能会出现如下这种情况:线程一在等线程二(释放某个对象),线程二又在等线程三,这样依次排下去直到有个线程在等线程一。这样就形成了一个环,每个线程都在等对方释放资源,而它们谁都不能运行。这就是所谓的死锁(deadlock)。
如果程序一运行就死锁,那倒也简单了。你可以马上着手解决这个问题。但真正的麻烦在于,程序看上去能正常运行,但是却潜伏着会引起死锁的隐患。或许你认为这里根本就不可能会有死锁,而bug也就这样潜伏下来了。直到有一天,让某个用户给撞上了(而且这种bug还很可能是不可重复的)。所以对并发编程来说,防止死锁是设计阶段的一个重要任务。
下面我们来看看由Dijkstra发现的经典的死锁场景:哲学家吃饭问题。原版的故事里有五个哲学家(不过我们的例程里允许有任意数量)。这些哲学家们只做两件事,思考和吃饭。他们思考的时候,不需要任何共享资源,但是吃饭的时候,就必须坐到餐桌旁。餐桌上的餐具是有限的。原版的故事里,餐具是叉子,吃饭的时候要用两把叉子把面条从碗里捞出来。但是很明显,把叉子换成筷子会更合理,所以:一个哲学家需要两根筷子才能吃饭。
现在引入问题的关键:这些哲学家很穷,只买得起五根筷子。他们坐成一圈,两个人的中间放一根筷子。哲学家吃饭的时候必须同时得到左手边和右手边的筷子。如果他身边的任何一位正在使用筷子,那他只有等着。
这个问题之所以有趣就在于,它演示了这么一个程序,它看上去似乎能正常运行,但是却容易引起死锁。你可以自己试试,用命令行参数调节哲学家的数量和思考的时间。如果有很多哲学家,而且/或者他们思考的时间很长,或许你永远也碰不到死锁,但是死锁的可能性总还是在的。默认的命令行参数会让它很快地死锁:
Chopstick和Philosopher都包含一个能自动递增的static counter。每个Philosopher有两个reference,一个表示左边那根Chopstick,另一个表示右边那根;Philosopher吃饭之前必须拿到这两根筷子。
static的ponder表示哲学家要花多长时间思考。如果这个值非零,则think( )会用它来生成一个随机的休眠时间。我们用这种方法证明,如果线程(也就是哲学家)花在其它事情上(思考)的时间多了,那么它们使用共享资源(筷子)的机会就少了,因而程序就不太容易死锁了,但事实并非如此。请看eat(
)。Philosopher先synchronized左边那根筷子。如果得不到,他就等,这时他处于阻塞状态。得到左边那根筷子之后,他又用相同的方法去申请右边的筷子。吃完之后,他也是先放左边的,再放右边的。Philosopher在run()里不停的思考和吃饭。
最近看马士兵的j2se中,讲到死锁那里的时候,马士兵老师讲到了五个哲学家吃饭谁都不肯舍弃自己的筷子,最终饿死的事情,其实就是一个死锁的问题,就是你不能访问别人,别人也不能访问你,你把自己锁在了自己的一个小圈子里。可以看出锁有好处,就是一旦上锁,在锁释放之前,没有其他人可以访问,从而保证了安全,但是生活中的我们不能为自己的生活加锁,看似保护了自己,实际上是与他人的距离在逐渐加大,保护私有物品,我们可以上锁,但是生活中的我们还是要向外开放的。
如果程序一运行就死锁,那倒也简单了。你可以马上着手解决这个问题。但真正的麻烦在于,程序看上去能正常运行,但是却潜伏着会引起死锁的隐患。或许你认为这里根本就不可能会有死锁,而bug也就这样潜伏下来了。直到有一天,让某个用户给撞上了(而且这种bug还很可能是不可重复的)。所以对并发编程来说,防止死锁是设计阶段的一个重要任务。
下面我们来看看由Dijkstra发现的经典的死锁场景:哲学家吃饭问题。原版的故事里有五个哲学家(不过我们的例程里允许有任意数量)。这些哲学家们只做两件事,思考和吃饭。他们思考的时候,不需要任何共享资源,但是吃饭的时候,就必须坐到餐桌旁。餐桌上的餐具是有限的。原版的故事里,餐具是叉子,吃饭的时候要用两把叉子把面条从碗里捞出来。但是很明显,把叉子换成筷子会更合理,所以:一个哲学家需要两根筷子才能吃饭。
现在引入问题的关键:这些哲学家很穷,只买得起五根筷子。他们坐成一圈,两个人的中间放一根筷子。哲学家吃饭的时候必须同时得到左手边和右手边的筷子。如果他身边的任何一位正在使用筷子,那他只有等着。
这个问题之所以有趣就在于,它演示了这么一个程序,它看上去似乎能正常运行,但是却容易引起死锁。你可以自己试试,用命令行参数调节哲学家的数量和思考的时间。如果有很多哲学家,而且/或者他们思考的时间很长,或许你永远也碰不到死锁,但是死锁的可能性总还是在的。默认的命令行参数会让它很快地死锁:
<strong><span style="font-family:KaiTi_GB2312;font-size:18px;"><strong>//: c13:DiningPhilosophers.java // Demonstrates how deadlock can be hidden in a program. // {Args: 5 0 deadlock 4} import java.util.*; class Chopstick { private static int counter = 0; private int number = counter++; public String toString() { return "Chopstick " + number; } } class Philosopher extends Thread { private static Random rand = new Random(); private static int counter = 0; private int number = counter++; private Chopstick leftChopstick; private Chopstick rightChopstick; static int ponder = 0; // Package access public Philosopher(Chopstick left, Chopstick right) { leftChopstick = left; rightChopstick = right; start(); } public void think() { System.out.println(this + " thinking"); if(ponder > 0) try { sleep(rand.nextInt(ponder)); } catch(InterruptedException e) { throw new RuntimeException(e); } } public void eat() { synchronized(leftChopstick) { System.out.println(this + " has " + this.leftChopstick + " Waiting for " + this.rightChopstick); synchronized(rightChopstick) { System.out.println(this + " eating"); } } } public String toString() { return "Philosopher " + number; } public void run() { while(true) { think(); eat(); } } } public class DiningPhilosophers { public static void main(String[] args) { if(args.length < 3) { System.err.println("usage:\n" + "java DiningPhilosophers numberOfPhilosophers " + "ponderFactor deadlock timeout\n" + "A nonzero ponderFactor will generate a random " + "sleep time during think().\n" + "If deadlock is not the string " + "'deadlock', the program will not deadlock.\n" + "A nonzero timeout will stop the program after " + "that number of seconds."); System.exit(1); } Philosopher[] philosopher = new Philosopher[Integer.parseInt(args[0])]; Philosopher.ponder = Integer.parseInt(args[1]); Chopstick left = new Chopstick(), right = new Chopstick(), first = left; int i = 0; while(i < philosopher.length - 1) { philosopher[i++] = new Philosopher(left, right); left = right; right = new Chopstick(); } if(args[2].equals("deadlock")) philosopher[i] = new Philosopher(left, first); else // Swapping values prevents deadlock: philosopher[i] = new Philosopher(first, left); // Optionally break out of program: if(args.length >= 4) { int delay = Integer.parseInt(args[3]); if(delay != 0) new Timeout(delay * 1000, "Timed out"); } } } ///:~ </strong></span></strong>
Chopstick和Philosopher都包含一个能自动递增的static counter。每个Philosopher有两个reference,一个表示左边那根Chopstick,另一个表示右边那根;Philosopher吃饭之前必须拿到这两根筷子。
static的ponder表示哲学家要花多长时间思考。如果这个值非零,则think( )会用它来生成一个随机的休眠时间。我们用这种方法证明,如果线程(也就是哲学家)花在其它事情上(思考)的时间多了,那么它们使用共享资源(筷子)的机会就少了,因而程序就不太容易死锁了,但事实并非如此。请看eat(
)。Philosopher先synchronized左边那根筷子。如果得不到,他就等,这时他处于阻塞状态。得到左边那根筷子之后,他又用相同的方法去申请右边的筷子。吃完之后,他也是先放左边的,再放右边的。Philosopher在run()里不停的思考和吃饭。
最近看马士兵的j2se中,讲到死锁那里的时候,马士兵老师讲到了五个哲学家吃饭谁都不肯舍弃自己的筷子,最终饿死的事情,其实就是一个死锁的问题,就是你不能访问别人,别人也不能访问你,你把自己锁在了自己的一个小圈子里。可以看出锁有好处,就是一旦上锁,在锁释放之前,没有其他人可以访问,从而保证了安全,但是生活中的我们不能为自己的生活加锁,看似保护了自己,实际上是与他人的距离在逐渐加大,保护私有物品,我们可以上锁,但是生活中的我们还是要向外开放的。
相关文章推荐
- 哲学家吃饭问题 代码实现如何 避免线程死锁
- 并发多线程之死锁-----哲学家吃饭问题
- 信号量(semaphore),互斥锁(mutex)解决哲学家死锁问题
- 爸爸妈妈儿子女儿吃水果问题以及五个哲学家吃饭问题
- java实现哲学家进餐问题,及其死锁问题的解决
- java 多线程 避免死锁 哲学家就餐问题
- 哲学家吃饭问题(资源加锁和超时释放)
- 哲学家问题之死锁:A Difficult Philosopher Problem
- java 多线程 死锁 哲学家就餐问题
- Thking in Java---从哲学家就餐问题看死锁现象
- 死锁原因,解决,避免方法及哲学家就餐问题
- 5位哲学家吃饭问题(算法描述)
- 如何防止哲学家就餐问题而引起的死锁
- 【Java】同步关键字就解决哲学家吃饭问题
- JavaSE第一百零四讲:哲学家就餐问题、死锁与使用wait及notify方法实现线程之间的相互通信
- 进程同步互斥——不死锁的哲学家问题
- java多线程哲学家思考吃饭问题
- 哲学家吃饭问题
- Java总结(十)—实现Runnable接口创建线程,线程安全同步,死锁(哲学家进餐问题),读写锁