问题描述:指定有五个哲学家(当然可以是n个哲学家),每个哲学家有两件事必须做,第一件事就是思考,第二件事就是吃饭,当他们思考的时候不需要任何的共享资源,我们可以看作是是一个独立的事件,当他们就餐的时候,就需要餐具来进行就餐。他们围坐在一个圆桌前面,每两个哲学家之间就有一个筷子,我们知道筷子只有两个的时候,才可以方便使用,当一个哲学家要吃饭的时候,那就需要拿起他两边的筷子,如果筷子被别人拿起了,则该哲学家就需要等待。其他人释放这个筷子才可以吃饭。
问题分析:如果一开始每一个哲学家都比较饿,先吃饭后思考,当他们想要进餐的时候,就会在筷子上面产生竞争,现在有这么一种情况,那便是每个哲学家都拿起了右边的筷子,那么便会出现死锁的情况,下面我们来看一个例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
public class Philosopher { public static void main(String[] args) throws Exception { int ponder = 5 ; if (args.length > 0) { ponder = Integer.parseInt(args[0]); } int size = 5 ; if (args.length > 1) { size = Integer.parseInt(args[1]); } ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); Chopstick[] sticks = new Chopstick[size]; for (int i = 0; i < size; i++) { sticks[i] = new Chopstick(); newCachedThreadPool.execute(new PhilosopherTask(sticks[i], sticks[(i + 1) % size], i, ponder)); } if (args.length == 3 && args[2].equals("timeout")) { TimeUnit.SECONDS.sleep(5); } else { System.out.println("press 'Entry' to quit"); System.in.read(); } newCachedThreadPool.shutdownNow(); } }
class PhilosopherTask implements Runnable { private Chopstick left ; private Chopstick right ; private final int id ; private final int phonderFactory ; private Random rand = new Random(47) ; public PhilosopherTask(Chopstick l, Chopstick r, int id, int phonder) { this.left = l ; this.right = r ; this.id = id ; this.phonderFactory = phonder ; }
public void pause () throws InterruptedException { if (this.phonderFactory == 0) { return ; } TimeUnit.MILLISECONDS.sleep(rand.nextInt(phonderFactory + 250)); } @Override public void run() { try { while (Thread.interrupted()) { System.out.println(this + " thinking"); pause(); System.out.println(this + " take right"); this.right.take(); System.out.println(this + " take left"); this.left.take(); System.out.println(this + "eating"); pause(); this.left.drop(); this.right.drop(); } } catch (InterruptedException e) { System.out.println(this + " exiting via interrupt"); } } @Override public String toString() { return "PhilosopherTask " + this.id; } }
class Chopstick { private boolean taken ; public synchronized void take () throws InterruptedException { while (taken) { wait(); taken = true ; } } public synchronized void drop () { taken = false ; } }
press 'Entry' to quit
|
这个版本就是典型的会出现死锁的情况,我们可以把思考的时间因子调整到0,便会100%的出现死锁的情况,那么如何避免上述所出现的死锁的现象呢,我们接着分析。
这里有是死锁必然会出现的前置条件,一共有一下这4点,当满足这四点的时候出现死锁,而如果这四点里面只要一点没有满足,则死锁情况造成不了,
- 1、互斥条件,任务使用的资源至少有一个是不能共享的
- 2、至少有一个任务获取了一个资源,并且等待下一个被别的线程所持有的资源。
- 3、资源不能被抢占,必须按照锁机制来运行。
- 4、存在循环等待的情况。
要存在死锁发生就必须达到上述的四个条件,缺一不可,那么要解决死锁问题,那么只需要破坏上述所述的这4个条件即可。这里最容易破坏掉的便是最后一条,因为最后一条是循环等待,如果我们让最后一个哲学家拿起左边的筷子,那么第四个情况也就算是被破坏了,这样遍可以解决哲学家就餐问题。(这只是最为简单的一种方式)当然还有其他比较优雅的方式。本节暂不讨论。
通过上述的思路,我们来实现我们的代码。来验证上述猜想是否正确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class Philosopher { public static void main(String[] args) throws Exception { int ponder = 5 ; if (args.length > 0) { ponder = Integer.parseInt(args[0]); } int size = 5 ; if (args.length > 1) { size = Integer.parseInt(args[1]); } ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); Chopstick[] sticks = new Chopstick[size]; for (int i = 0; i < size; i++) { sticks[i] = new Chopstick(); if (i < size - 1) { newCachedThreadPool.execute(new PhilosopherTask(sticks[i], sticks[i + 1], i, ponder)); } else { newCachedThreadPool.execute(new PhilosopherTask(sticks[0], sticks[i], i, ponder)); } } if (args.length == 3 && args[2].equals("timeout")) { TimeUnit.SECONDS.sleep(5); } else { System.out.println("press 'Entry' to quit"); System.in.read(); } newCachedThreadPool.shutdownNow(); } }
|
修改了相应的初始化顺序,这样遍不会造成死锁了。