java多執行緒--哲學家就餐問題
在使用java中的sychronized或者顯示鎖來進行互斥操作時,就可能會出現死鎖的情況:即任務一獲得A資源,等待B資源。任務二獲得B資源等待C資源。任務三獲得C資源等待D資源。而任務四獲得D資源等待A資源,這樣就造成一個連續的迴圈等待,沒有哪個執行緒能夠繼續下去,稱為死鎖。
對於死鎖,哲學家就餐問題就是一個經典的例子。
問題描述[1]:
在一個圓桌上,有n個哲學家,n只筷子,每個哲學家左右兩邊各返一隻筷子。哲學家可以進行思考和吃飯,思考時,不獲取筷子。吃飯時,必須同時獲得左右兩隻筷子才能吃飯(先獲得右邊,再獲得左邊)。
先來看看chopstick類:
package philosopher;
/**
* 滿足:
* 每根筷子同時只能被一個哲學家獲得,若有另外一個哲學家請求獲得該筷子,則需要等待
* 哲學家使用完筷子之後就放回並通知其他哲學家使用
* @author lecky
*
*/
public class Chopstick {
private int index;
private boolean use = false;
public Chopstick(int index) {
super();
this.index = index;
}
@Override
public String toString() {
return "Chopstick [index=" + index + "]";
}
/*
* 獲得筷子
* 該筷子被獲得之後,當有其他哲學家執行緒來請求獲得時,都需要等待
*/
public synchronized void take() throws InterruptedException{
while(use)
wait();
use =true;
}
/*
* 歸還筷子
* 當持有該筷子的哲學家使用完畢之後,就將其歸還,並通知其他在等待該筷子資源的哲學家
*/
public synchronized void drop(){
use = false;
notifyAll();
}
}
Philosopher類:
哲學家先獲得右邊的筷子,再獲得左邊的筷子。在獲得左邊的筷子時,若左邊的筷子已經被相鄰哲學家獲得,則需要等待直到其釋放該資源為止,即left.take()會阻塞,直到被通知。
package philosopher;
import java.util.Random;
/**
* 每個哲學家可以進行思考或者吃飯,吃飯時需要先後獲得右邊和左邊的筷子
* 若沒有同時獲得右邊和左邊的筷子,則等待,
* 若使用完之後就返回。
* @author lecky
*
*/
public class Philosopher implements Runnable{
private Chopstick right ;
private Chopstick left;
private int index;
private int thinkTime;
public Philosopher(Chopstick right, Chopstick left, int index, int thinkingTime) {
super();
this.right = right;
this.left = left;
this.index = index;
this.thinkTime = thinkingTime;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
System.out.println(this + " thinking .......");
thinking();
System.out.println(this+" start to eat and take right stick");
right.take();
System.out.println(this+" take left stick");
left.take();
System.out.println(this+" eating");
thinking();//吃飯
right.drop();
left.drop();
}
} catch (InterruptedException e) {
System.out.println(this+"InterruptedException");
}
}
/**
* 哲學家思考時間,由thinkingTime因子決定
* @throws InterruptedException
*/
private void thinking() throws InterruptedException{
Thread.sleep(thinkTime*100);
}
@Override
public String toString() {
return "Philosopher [index=" + index + "]";
}
}
產生死鎖的版本:
測試類中有5個哲學家和5只筷子,將思考時間定為0,就會很快出現死鎖的情況。
package philosopher;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Test;
/**
* 會產生死鎖的版本
* 5個哲學家,5只筷子,每個哲學家吃飯之前需要先拿到右邊的筷子,然後再拿到左邊的筷子
* 之後才能吃飯
* @author lecky
*
*/
public class DeadlockPhilosopher {
@Test
public void test() throws InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
int size=5;
int thinkingTime=0;
Chopstick[] chopstick = new Chopstick[size];
for(int i=0;i<size;i++)
chopstick[i] = new Chopstick(i);
for(int i=0;i<size;i++)
executor.execute(new Philosopher(chopstick[i], chopstick[(i+1)%size], i, thinkingTime));
Thread.sleep(4*1000);
executor.shutdownNow();
}
}
執行結果:
分析執行結果會發現,
Philosopher [index=0]獲得right stick[0],請求left stick
Philosopher [index=1]獲得right stick[1],請求left stick
Philosopher [index=2]獲得right stick[2],請求left stick
Philosopher [index=3]獲得right stick[3],請求left stick
Philosopher [index=4]獲得right stick[4],請求left stick
而沒有一個能夠同時獲得兩隻筷子吃飯的哲學家。
Philosopher [index=0] thinking .......
Philosopher [index=3] thinking .......
Philosopher [index=4] thinking .......
Philosopher [index=1] thinking .......
Philosopher [index=4] start to eat and take right stick
Philosopher [index=3] start to eat and take right stick
Philosopher [index=2] thinking .......
Philosopher [index=0] start to eat and take right stick
Philosopher [index=2] start to eat and take right stick
Philosopher [index=3] take left stick
Philosopher [index=4] take left stick
Philosopher [index=1] start to eat and take right stick
Philosopher [index=2] take left stick
Philosopher [index=0] take left stick
Philosopher [index=1] take left stick
Philosopher [index=1]InterruptedException
Philosopher [index=0]InterruptedException
Philosopher [index=2]InterruptedException
Philosopher [index=3]InterruptedException
Philosopher [index=4]InterruptedException
產生死鎖的條件:(簡要)
- 互斥條件。至少有一個資源(筷子)只能同時被一個任務獲得。
- 迴圈等待。這裡每個哲學家都按照先獲得右邊的筷子,然後再獲得左邊的筷子的方式進行,那麼就會構成一個迴圈等待。
修改後的測試類:
破壞第二個產生死鎖的條件:迴圈等待。
將第五個哲學家就餐拿起筷子的順序更改,不使之構成迴圈條件。
package philosopher;
import static org.junit.Assert.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Test;
/**
* 破壞產生死鎖的迴圈條件
* 使第五個哲學家不按照先獲得右邊筷子,再獲得左邊筷子的方式進行
* @author lecky
*
*/
public class FixedPhilosopher {
@Test
public void test() throws InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
int size=5;
int thinkingTime = 0;
Chopstick[] chopsticks = new Chopstick[size];
for(int i=0;i<size;i++)
chopsticks[i]=new Chopstick(i);
for(int i=0;i<size-1;i++)
executor.execute(new Philosopher(chopsticks[i], chopsticks[i+1], i, thinkingTime));
executor.execute(new Philosopher(chopsticks[0], chopsticks[size-1], size, thinkingTime));//更改第五個哲學家獲得筷子的順序
Thread.sleep(100*1000);
executor.shutdownNow();
}
}
[1].thinking in java