併發多執行緒之死鎖-----哲學家吃飯問題
阿新 • • 發佈:2018-12-31
該例子說明了4點出現死鎖需要同時滿足的條件:
- 互斥條件:任務使用的資源至少有一個是不能共享的。這裡,一根chopstick(筷子)一次就只能讓一個philosopher(哲學家)使用。
- 至少有一個任務它必須持有一個資源且正在等待獲取另一個當前被別的任務持有的資源,也就是說,要發生死鎖,philosopher必須拿著一根筷子並且等待另一根筷子。
- 資源不能被任務搶佔,任務必須把資源釋放當作普通事件。philosopher很有禮貌,他們不會從其他的philosoper那裡搶筷子。
必須有迴圈等待,這時,一個任務等待其他任務所持有的資源,後者又在等待另一個任務所持有的資源,這樣一直下去直到有一個任務在等待第一個任務所持有的資源,使得大家都被鎖住。在DeadLockingDiningPhilosopher.java中,因為每個philosopher都試圖先得到右邊的筷子再得到左邊的筷子,所以發生迴圈等待。
因為要發生死鎖,所有這些條件都必須滿足,要防止死鎖的話,破壞其中一個條件即可,在程式中,防止死鎖的最容易的方法是破壞第4個條件,即循壞等待條件,本例中,就是最後一個哲學家先拿起左邊的筷子,再拿起右邊的筷子。這樣就可以防止迴圈等待。
Chockstick.java:筷子定義類
package philo;
public class Chopstick {
private boolean taken = false;
public synchronized void take() throws InterruptedException { //當一個任務拿起該筷子時,將taken賦值true,別的任務就在take()上等待
while(taken)
wait();
taken = true;
}
public synchronized void drop(){ //當持有筷子的任務drop時,taken賦值false,在該筷子物件的taken()上等待的任務就可以獲得該筷子
taken = false;
notifyAll();
}
}
Philosopher.java:哲學家定義類
package philo;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class Philosopher implements Runnable {
private Chopstick left;
private Chopstick right;
private final int id;
private final int ponderFactor;
private Random random = new Random(47);
private void pause() throws InterruptedException {
if(ponderFactor == 0) return;
TimeUnit.MILLISECONDS.sleep(random.nextInt(ponderFactor * 250));
}
public Philosopher(Chopstick left, Chopstick right, int ident, int ponder){
this.left = left;
this.right = right;
this.id = ident;
this.ponderFactor = ponder;
}
public void run() { //起一個任務(執行緒)
try {
while(! Thread.interrupted()) {
System.out.println(this +" " + "thinking");
pause();
System.out.println(this +" " + "grabbing right");
right.take(); //該哲學家首先拿起右邊的筷子
System.out.println(this + " " + "grabbing left");
left.take(); // 該哲學家然後拿起左邊的筷子,此處可能發生的情況:1、成功拿起;2、等待左邊的哲學家drop掉右手的筷子,
//若每一個哲學家都拿著右手的等著左手的筷子,並且最後一個哲學家左手等待第一個哲學家右手的筷子,就會循壞等待發生死鎖。
System.out.println(this + " " + "is eating");//若該哲學家成功拿起左右手筷子,則吃飯,下一步並釋放手中筷子
pause();
right.drop();
left.drop();
}
} catch (InterruptedException e) {
System.out.println(this + " " + "exiting via interruption");
}
}
public String toString(){
return "Philosopher" + id;
}
}
DeadLockingDiningPhilosopher.java :產生死鎖的哲學家吃飯main類:including main method
package philo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class DeadLockingDiningPhilosopher {
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 exec = Executors.newCachedThreadPool();
Chopstick[] sticks = new Chopstick[size];
for(int i=0; i<size; i++)
sticks[i] = new Chopstick();
for(int i =0; i< size; i++)
exec.execute(new Philosopher(sticks[i], sticks[(i+1) % size], i, ponder)); //n個哲學家n支筷子循壞使用
if(args.length == 3 && args[2].equals("timeout"))
TimeUnit.SECONDS.sleep(5);
else {
System.out.println("please Press 'Enter' to quit");
System.in.read();
}
exec.shutdown();
}
}
注:在這裡,需要輸入命令列引數,一共能三個引數分別是:arg[0]:哲學家思考、吃飯、放下筷子的間隔時間,這裡設定為0可以讓死鎖發生的機會變大;args[1]:哲學家數量;args[2]:timeout暫時沒想出用處 .