学习目标
哲学家就餐问题是在计算机科学中的一个经典问题,用来演示在并行计算中多线程同步时产生的问题。在1971年,著名的计算机科学家艾兹格·迪科斯彻提出了一个同步问题,即假设有五台计算机都试图访问五份共享的磁带驱动器。稍后,这个问题被托尼·霍尔重新表述为哲学家就餐问题。这个问题可以用来解释死锁和资源耗尽
从百度百科上解释来看:哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。总结来看哲学家问题就是:
接下来我们就将上述描述抽象出来,并用Java并发基础来解决哲学家就餐问题。
初步解决
针对上述问题我们可以简单定个协议规则:
- 规则1:每个哲学家(线程)先拿起左边的叉子,再拿右边的叉子。
- 规则2:如果拿不到就等待。
那么我可以抽象如下:
- id(1-5)用来描述不同的哲学家
- state三种状态:thinking、hungry、eating
- 方法有拿起和放下叉子:takeLeft、takeRight、putLeft和putRight
那么我们可以得到如下程序:
哲学家抽象
/**
* 哲学家抽象
*/
public class Philosopher implements Runnable{
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
String state;
int id;
// 用来统计哲学家完成的次数
int count = 0;
// 用来统计总共完成的次数
static AtomicInteger total = new AtomicInteger(0);
// 开始时间
static long startMills = System.currentTimeMillis();
public Philosopher(int id){
this.id = id;
this.state = "Thinking";
}
/**
* 思考后状态是Hungry
*/
public void thinking() throws InterruptedException {
if(this.state == "Thinking") {
Thread.sleep((long)(Math.random()*100));
this.state = "Hungry";
}
}
/**
* 修改状态为Eating并模拟一段时间
*/
public void eating() throws InterruptedException {
this.state = "Eating";
if(Math.random() > 0.9) {
Thread.sleep(100000);
} else {
Thread.sleep((long)(Math.random()*100));
}
}
public int left(){
return this.id - 1;
}
public int right(){
// %5是因为哲学家id是从1-5,方便下标操作
return this.id % 5;
}
/**
* 修拿起叉子需要判断当前叉子是否已被拿
*/
private boolean _take(int[] forks, int fork) {
if(forks[fork] == 0) {
forks[fork] = this.id;
return true;
}
return false;
}
protected boolean takeLeft(int[] forks) {
return this._take(forks, this.left());
}
protected boolean takeRight(int[] forks) {
return this._take(forks,