參考:https://blog.csdn.net/fuziwang/article/details/79809994
1.問題描述:
哲學家進餐問題描述有五個哲學家,他們的生活方式是交替地進行思考和進餐,哲學家們共用一張圓桌,分別坐在周圍的五張椅子上,在圓桌上有五個碗和五支筷子,平時哲學家進行思考,飢餓時便試圖取其左、右最靠近他的筷子,只有在他拿到兩支筷子時才能進餐,該哲學家進餐完畢后,放下左右兩只筷子又繼續思考。
約束條件
(1)只有拿到兩只筷子時,哲學家才能吃飯。
(2)如果筷子已被別人拿走,則必須等別人吃完之后才能拿到筷子。
(3)任一哲學家在自己未拿到兩只筷子吃完飯前,不會放下手中已經拿到的筷子。
2.求解方法
(1).信號量的設置
放在桌子上的筷子是臨界資源,在一段時間內只允許一位哲學家使用,為了實現對筷子的互斥訪問,可以用一個信號量表示筷子,由這五個信號量構成信號量數組。
semaphore chopstick[5] = {1,1,1,1,1};
while(true)
{
/*當哲學家飢餓時,總是先拿左邊的筷子,再拿右邊的筷子*/
wait(chopstick[i]);
wait(chopstick[(i+1)%5]);
// 吃飯
/*當哲學家進餐完成后,總是先放下左邊的筷子,再放下右邊的筷子*/
signal(chopstick[i]);
signal(chopstick[(i+1)%5]);
}
上述的代碼可以保證不會有兩個相鄰的哲學家同時進餐,但卻可能引起死鎖的情況。
假如五位哲學家同時飢餓而都拿起的左邊的筷子,就會使五個信號量chopstick都為0,當他們試圖去拿右手邊的筷子時,都將無筷子而陷入無限期的等待。
(2)避免死鎖
策略一
原理:至多只允許四個哲學家同時進餐,以保證至少有一個哲學家能夠進餐,最終總會釋放出他所使用過的兩支筷子,從而可使更多的哲學家進餐。定義信號量count,只允許4個哲學家同時進餐,這樣就能保證至少有一個哲學家可以就餐。
semaphore chopstick[5]={1,1,1,1,1};
semaphore count=4; // 設置一個count,最多有四個哲學家可以進來
void philosopher(int i)
{
while(true)
{
think();
wait(count); //請求進入房間進餐 當count為0時 不能允許哲學家再進來了
wait(chopstick[i]); //請求左手邊的筷子
wait(chopstick[(i+1)%5]); //請求右手邊的筷子
eat();
signal(chopstick[i]); //釋放左手邊的筷子
signal(chopstick[(i+1)%5]); //釋放右手邊的筷子
signal(count); //退出房間釋放信號量
}
}
策略二
原理:僅當哲學家的左右兩支筷子都可用時,才允許他拿起筷子進餐。可以利用AND 型信號量機制實現,也可以利用信號量的保護機制實現。利用信號量的保護機制實現的思想是通過記錄型信號量mutex對取左側和右側筷子的操作進行保護,使之成為一個原子操作,這樣可以防止死鎖的出現。描述如下:
semaphore chopstick[5]={1,1,1,1,1};
void philosopher(int i)
{
while(true)
{
/* 這個過程中可能只能由一個人在吃飯 */
think();
wait(mutex); // 保護信號量
wait(chopstick[(i+1)%5]); // 請求右手邊的筷子
wait(chopstick[i]); // 請求左手邊的筷子
signal(mutex); // 釋放保護信號量
eat();
signal(chopstick[(i+1)%5]); // 釋放右手邊的筷子
signal(chopstick[i]); // 釋放左手邊的筷子
}
}
策略三
原理:規定奇數號的哲學家先拿起他左邊的筷子,然后再去拿他右邊的筷子;而偶數號的哲學家則先拿起他右邊的筷子,然后再去拿他左邊的筷子。按此規定,將是1、2號哲學家競爭1號筷子,3、4號哲學家競爭3號筷子。即五個哲學家都競爭奇數號筷子,獲得后,再去競爭偶數號筷子,最后總會有一個哲學家能獲得兩支筷子而進餐。
semaphore chopstick[5]={1,1,1,1,1};
void philosopher(int i)
{
while(true)
{
think();
if(i%2 == 0) //偶數哲學家,先右后左。
{
wait (chopstick[(i + 1)%5]) ;
wait (chopstick[i]) ;
eat();
signal (chopstick[(i + 1)%5]) ;
signal (chopstick[i]) ;
}
else //奇數哲學家,先左后右。
{
wait (chopstick[i]) ;
wait (chopstick[(i + 1)%5]) ;
eat();
signal (chopstick[i]) ;
signal (chopstick[(i + 1)%5]) ;
}
}
3.代碼實現
用java代碼實現第二種方法:
public class ThreadTest{
public static void main(String[] args) {
Fork fork = new Fork();
new Philosopher("0", fork).start();
new Philosopher("1", fork).start();
new Philosopher("2", fork).start();
new Philosopher("3", fork).start();
new Philosopher("4", fork).start();
}
}
class Philosopher extends Thread{
private String name;
private Fork fork;
public Philosopher(String name, Fork fork) {
super(name);
this.name = name;
this.fork = fork;
}
@Override
public void run() {
while (true){
thinking();
fork.takeFork();
eating();
fork.putFork();
}
}
public void eating(){
System.out.println("I am Eating:"+name);
try {
sleep(1000);//模擬吃飯,占用一段時間資源
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void thinking(){
System.out.println("I am Thinking:"+name);
try {
sleep(1000);//模擬思考
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Fork getFork() {
return fork;
}
public void setFork(Fork fork) {
this.fork = fork;
}
}
class Fork{
/*5只筷子,初始為都未被用*/
private boolean[] used={false,false,false,false,false,false};
public synchronized void takeFork(){
String name = Thread.currentThread().getName();
int i = Integer.parseInt(name);
while(used[i]||used[(i+1)%5]){
try {
wait();//如果左右手有一只正被使用,等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
used[i ]= true;
used[(i+1)%5]=true;
}
public synchronized void putFork(){
String name = Thread.currentThread().getName();
int i = Integer.parseInt(name);
used[i]= false;
used[(i+1)%5]=false;
notifyAll();//喚醒其他線程
}
}