Title 哲学家就餐问题是在1965年由Dijkstra提出并解决的一个问题,后来成为同步问题的一个经典问题。
Disciber 有五位哲学家围坐在一张桌子前, 他们面前都放了一碗面,彼此间放了一把叉子。因为面非常的滑,以至于使用一个叉子吃不了,所以每位哲学家需要拿起放在他左右两边的叉子吃面。同时,每位哲学家具有两种状态:进餐、思考问题。每当一位哲学家感到饥饿时,他会试图去获得位于他左右两边的那两把叉子,每次都只能拿一把,先后顺序无所谓,两把叉子都拿到后才能开始进餐,吃完了以后,他需要把两把叉子返回原处,然后继续思考。
Question 五个哲学家,五碗面和五个叉子,每碗面需要两个叉子,如何进行操作?
Analysis 1、 这里每位哲学家都具有各自的面,而相互之间竞争的是吃面的叉子,也就是说叉子是共享资源。但他们竞争的叉子也是有限制,也就是当前哲学家左右两边的叉子,而不能是其他叉子。如果进行抽象建模,可以得出如下模型:
循环:
思考问题;
尝试拿左边叉子;
尝试拿右边叉子;
吃面;
放下左边叉子;
放下右边的叉子;
所以我们可以采用缓冲区的方式,对叉子的使用进行控制,有权拿叉子的哲学家则分配,无权拿叉子的哲学家则需要阻塞。
Analysis 2、这里的每位哲学家都具有各自的面,而他们的状态是吃面、思考,那么每次都只能让其中的两位吃面,其他的要么在思考,要么在等待。当某个哲学家在吃面时,哪些哲学家可以吃面,哪些要等待。其实就是当前正在吃面的哲学家的左右两边的哲学家不能吃面,要么思考,要么等待,当哲学家吃完,则需要告诉左右两边还在等待的哲学家他们可以申请吃面的权利了。
循环:
思考问题;
申请权利;
吃面;
放弃权利;
通知;
这里我们采用第二套分析方案:
import java.io.File;
import java.io.FileWriter;
public class Philosopher extends Thread {
private int state; //表示哲学家的状态,0思考、1表示饥饿和2表示正在吃饭
private Philosopher left; //左边的哲学家
private Philosopher right; //右边的哲学家
private String name; //记录自己的名字
public Philosopher(final String name) {
this.name = name;
}
public int getPState() {
return this.state;
}
public void setPState(final int state) {
this.state = state;
}
public void setLeft(final Philosopher philosopher) {
this.left = philosopher;
}
public Philosopher getLeft(){
return this.left;
}
public void setRight(final Philosopher philosopher) {
this.right = philosopher;
}
public Philosopher getRight(){
return this.right;
}
/**
* 判断当前是否具备吃的条件.
*/
public boolean canEat() {
//当前哲学家处于饥饿状态,而且左右两边的哲学家还没有一个人处于吃的状态,那么当前哲学家就可以吃
if(this.getPState() == 1 && this.getLeft().getPState() != 2
&& this.getRight().getPState() != 2) {
return true;
}
return false;
}
/**
* 哲学家吃饭....
*/
public synchronized void eating() {
/*ERROR: Exception Current Thread not owner,也就是wait、notify都只能在当前线程中访问,而其他线程不能访问
try { //首先堵住左右两边的哲学家进行竞争
//阻塞左边的哲学家
if(this.getLeft().getPState() == 1) {
this.getLeft().wait();
}
//阻塞右边的哲学家
if(this.getRight().getPState() == 1) {
this.getRight().wait();
}
} catch (Exception e) {
e.printStackTrace();
} */
this.setPState(2); //将当前哲学家的状态置为2,从而阻塞左右两边的线程
//阻塞左边的哲学家
if(this.getLeft().getPState() == 1) {
this.getLeft().PEat();
}
//阻塞右边的哲学家
if(this.getRight().getPState() == 1) {
this.getRight().PEat();
}
System.out.println(this.name + " 正在吃饭....");
try{
Thread.sleep(300); // 吃饭时间0.3秒
} catch(Exception e) {
e.printStackTrace();
}
System.out.println(this.name + " 吃完了!");
this.setPState(0); // 重新回到思考问题的状态
}
/**
* 哲学家思考问题....
*/
public void thinking() {
System.out.println(this.name + "正在思考问题....");
/*ERROR: 这里有问题,因为Thread.sleep不会让出对象锁,那么很可能会造成一个问题
那就是当前对象释放了锁的同时,很快又得到了锁,从而锁住左右两边的对象,陷入死锁。
try{
Thread.sleep(1000); //哲学家思考1秒
} catch(Exception e) {
e.printStackTrace();
} */
//这里让哲学家干点事,不然执行太快
/*#WARNING: 想通过写文件的方式,不行。因为CPU执行和文件读写是区分的。
try {
File file = new File("./question.txt");
if(!file.exists()) {
file.createNewFile();
}
FileWriter writer = new FileWriter(file);
writer.append(this.name + " 正在思考 1 + 1 = ? \n");
writer.close();
} catch (Exception e) {
e.printStackTrace();
} */
double i = 0.0;
while (i < 100000000) {
i += 0.1;
}
this.setPState(1); //思考完了以后就饿了
}
/**
* 给每一个哲学家一个信号量
*/
public synchronized void PEat() {
if(!this.canEat()) {
try {
this.wait();
} catch(Exception e) {
e.printStackTrace();
}
}
}
public synchronized void VEat() {
/*#ERROR: Exception Current Thread not owner,也就是wait、notify都只能在当前线程中访问,而其他线程不能访问
//唤醒左边的哲学家
if(this.getLeft().getPState() == 1) {
this.getLeft().notify();
}
//唤醒右边的哲学家
if(this.getRight().getPState() == 1) {
this.getRight().notify();
} */
this.notify();
}
public void run() {
while(true) {
thinking();
this.PEat();
this.eating();
//唤醒左边的哲学家
if(this.getLeft().getPState() == 1) {
this.getLeft().VEat();
}
//唤醒右边的哲学家
if(this.getRight().getPState() == 1) {
this.getRight().VEat();
}
}
}
}
测试类:
public class PhilosopherEatingQuestion {
public static void main(String[] args) {
/**
* 任意时刻都只能有两个对象在吃饭。
*/
Philosopher pher1 = new Philosopher("Lao Tzu");
Philosopher pher2 = new Philosopher("Confucius");
Philosopher pher3 = new Philosopher("Plato");
Philosopher pher4 = new Philosopher("Aristotle");
Philosopher pher5 = new Philosopher("Decare");
//现在来排位置
pher1.setLeft(pher5);
pher1.setRight(pher1);
pher2.setLeft(pher1);
pher2.setRight(pher3);
pher3.setLeft(pher2);
pher3.setRight(pher4);
pher4.setLeft(pher3);
pher4.setRight(pher5);
pher5.setLeft(pher4);
pher5.setRight(pher1);
// 启动
pher1.start();
pher2.start();
pher3.start();
pher4.start();
pher5.start();
}
}