哲学家就餐问题
问题描述:
哲学家就餐问题,是并行程序中的一个经典问题,描述如下:
1、圆桌上有5位哲学家、每两位中间有一个筷子
2、每个哲学家有两件事要做
思考
吃饭(哲学家必须同时拿到两个筷子才能吃饭)
3、哲学家之间并不知道对方何时要吃饭、何时要思考,不能协商制定吃饭、思考策略
4、制定一个拿筷子的策略,使得哲学家不会因为拿筷子而出现死锁乐观锁
经典解法
-
资源分级的解法
为资源(筷子)分配一个偏序结构,所有资源必须按照顺序访问,即拿起筷子必须按照一定的顺序。
如:先拿编号较小的筷子,再拿编号较大的筷子,放下筷子的顺序则无所谓,这样按照筷子编号较小的优先拿则4号(编号最大)筷子没人拿,则其中一位哲学家就可以获取到2个筷子进行就餐,避免了死锁
劣势:效率低下,实际问题中,计算机无法知道自己所需要的资源有哪一些,无法编号,且本身编号在实际使用中会先释放已拿到的才能获取待获取的,但本身资源都需要 -
仲裁者解决方案
引入一个仲裁者,哲学家为了拿到筷子必须向仲裁者发送请求,仲裁者每次只服务一个哲学家,直到它拿到两个筷子为止,仲裁者必须要使用锁实现
劣势:这种解决方案限制了并发性,也限制了在筷子允许的情况下,多个哲学家同时吃饭的可能性中断响应 -
chandy/misra解法
这是哲学家就餐问题的另一种解法,允许任意的用户(编号P1,…,Pn)争用任意数量的资源。与资源分级解法不同,这里编号随意- 对每一个哲学家编号 对每一个竞争一个筷子资源的哲学家,新拿一个筷子,给编号较低的哲学家
- 每只筷子有两种状态:“干净的”或者“”脏的,初始所有筷子都是脏的
- 当一个哲学家要吃饭他必须拥有两个筷子,当他缺乏某只筷子的时候,发起请求
- 当拥有筷子的哲学家收到请求时,如果筷子是干净的,那么他继续留着使用,否则就擦干净并交出筷子
- 哲学家吃完的时候,筷子变脏,当有邻座请求筷子时,擦干净筷子,然后给邻座
代码实现:
以下代码是使用的第一种解法,也就是对资源的分级算法,完成本题。
public class PhilosopherDinner {
//就餐人数
private static final int member = 5;
private static Random random = new Random();
static tableware[] tablewares = new tableware[member];
//餐具
static class tableware{
private int user = 0;
private int index;
tableware(int index){
this.index = index;
}
public synchronized int getUser() {
return user;
}
public synchronized void setUser(int user) {
this.user = user;
}
public synchronized int getIndex(){
return index;
}
}
//哲学家
static class Philosopher implements Runnable {
private int NO;
Philosopher(int NO){
this.NO = NO;
}
@Override
public void run() {
while (true) {
//思考
int thinkTime = random.nextInt(10000);
try {
System.out.println("哲学家" + NO + "思考: " +thinkTime/1000+ "秒" );
Thread.sleep(thinkTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
//吃饭
System.out.println("哲学家" + NO + " 准备吃饭");
//需求筷子是NO坐标及坐标+1 对应数组下标
int t1 = NO-1;
int t2 = NO%member;
while(true){
if(request(t1) && request(t2)){//获得餐具
int nextInt = random.nextInt(10000);
System.out.println("哲学家" + NO + " 正在吃饭,预计"+nextInt/1000 + "秒");
try {
Thread.sleep(nextInt);
} catch (InterruptedException e) {
e.printStackTrace();
}
//吃完放回筷子
tablewares[t1].setUser(0);
tablewares[t2].setUser(0);
System.out.println("哲学家" + NO + " 吃完饭了"+
" 归还" + tablewares[t1].getIndex()+
" 归还" + tablewares[t2].getIndex());
break;
}
}
}
}
//获取餐具
private synchronized boolean request(int index) {
int userIndex = tablewares[index].getUser();
if(userIndex == NO)
return true;
if(userIndex == 0){
tablewares[index].setUser(NO);
System.out.println("哲学家"+NO+"获得餐具"+(index+1));
return true;
}
return false;
}
}
public static void main(String[] args) {
//初始化
for(int i=0;i<member;i++){
tableware tableware = new tableware(i+1);
tablewares[i] = tableware;
Philosopher philosopher = new Philosopher(i+1);
Thread thread = new Thread(philosopher);
thread.setName("哲学家"+(i+1));
thread.start();
}
}
}
代码中,要就餐的哲学家会首先拿自己编号下标的餐具,若未取到,则获取餐具返回false的if判断就不会成功,也就是说不会进行&&右边的获取,即不会获取下标+1号餐具。用这种方法,实现了资源的分级。