哲学家就餐

问题描述:指定有五个哲学家(当然可以是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
/**
* 哲学家就餐问题
* 问题描述:指定有五个哲学家(当然可以是n个哲学家),每个哲学家有两件事必须做,第一件事就是思考,第二件事就是吃饭,
* 当他们思考的时候不需要任何的共享资源,我们可以看作是是一个独立的事件,当他们就餐的时候,就需要餐具来进行就餐。
* 他们围坐在一个圆桌前面,每两个哲学家之间就有一个筷子,我们知道筷子只有两个的时候,才可以方便使用,当一个哲学家要吃饭的时候,
* 那就需要拿起他两边的筷子,如果筷子被别人拿起了,则该哲学家就需要等待。其他人释放这个筷子才可以吃饭。
*
* @author pengchengliu
*
*/

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();
}
}
/**
* 哲学家任务,每一个哲学家左边还有右边各是一个筷子
* 还有一个思考工厂
* @author pengchengliu
*
*/
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 ;
}

/**
* 思考相关的方法,通过一个传入的思考因子来决定。
* @throws InterruptedException
*/
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;
}
}
/**
* 筷子类,共享资源,(一共有5个)通过一个标示位,
* 来控制当前筷子的状态是可以用还是不可用。
* 由于该筷子又可能存在竞争条件,所以加锁是必须的。
* @author pengchengliu
*
*/
class Chopstick {
private boolean taken ;
public synchronized void take () throws InterruptedException {
while (taken) {
wait();
taken = true ;
}
}

public synchronized void drop () {
taken = false ;
}
}

//out
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();
}
}

修改了相应的初始化顺序,这样遍不会造成死锁了。