记得大二时学操作系统,有个关于锁的经典模型——哲学家模型。当时老师要求我们用程序实现,我当时没做出来,一直耿耿于怀。今天看Java cookbook中的线程介绍。于是动手来试下,花了一天才弄出来。在此把大概过程罗列如下,也算是交几年前的作业吧。
程序的设计分两部分:哲学家问题的逻辑处理、图形化展示结果部分。最终效果如下:
图1:初始时五个哲学家都在睡眠
图2::哲学家0、3在睡眠,1、4在吃饭,2挨饿
图3:哲学家0、3在吃饭,1在挨饿,2、4在睡眠
首先做一点约定:
对于哲学家按顺时针计算,最顶部的哲学家为0,右边的为1,右下角为2……。
对于筷子见图1,哲学家0、1夹着的筷子为0,按顺时针递增,即1、2中间的筷子为1……
至于什么是哲学家问题,若忘记了可以翻阅下操作系统温习下,简单的描述下:有五个哲学家要吃饭,同时只有五个筷子,为了吃饭必须同时获得两个筷子。如果每个哲学家都是先拿左边筷子再拿右边筷子,则必然发生死锁。所以规定了奇数的先拿左边,偶数的先拿右边。由此可见,筷子是竞争资源。准确的讲,
筷子0是哲学家0、1竞争的资源;
筷子1是哲学家1、2竞争的资源;
筷子2是哲学家2、3竞争的资源;
筷子3是哲学家3、4竞争的资源;
筷子4是哲学家4、0竞争的资源;
类图如下:
所有的逻辑都在Person中完成。Person是一个继承了Thread的线程。在PhilosopherDining的main方法中实例化了五个Person并start了他们。现在我们看下Person的run方法及其涉及的其他方法:
/**
* 不断的睡眠、吃饭.
*
* @see java.lang.Thread#run()
*/
public void run() {
while (!done) {
sleeping();
eat();
}
}
/**
* 睡眠几秒钟.
*/
public void sleeping() {
try {
// id睡觉
guiMgr.setPersonSleeping(id);
System.out.println("Person[" + id + "] is sleeping...");
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 吃饭,必须同时得到左右筷子才能吃.
*/
public void eat() {
// 奇数的先拿左边
if (isOdd(id)) {
// 先拿左边
getChopstick(leftNo);
// 再拿右边
getChopstick(rightNo);
} else {
// 偶数的先拿右边
// 先拿右边
getChopstick(rightNo);
// 再拿左边
getChopstick(leftNo);
}
// 两边都拿到的话则可以放心的吃东西了
eating();
}
很简单,先睡眠几分钟,饿醒了就吃东西。吃东西又需要先拿左/右边的筷子再拿右/左边的筷子,然后才能吃。关键就在拿筷子getChopstick、吃饭eating这两件事上。
/**
* 试图获取筷子,若被他人先拿则需等待.
*
* @param chopstickNo
* 要拿的筷子号
*/
private void getChopstick(final int chopstickNo) {
System.out.println("id:" + id + " no:" + chopstickNo);
synchronized (chopsticks[chopstickNo]) {
while (chopsticks[chopstickNo].isUsed()) {
System.out.println("Person[" + id + "] is waiting Chopstick["
+ chopstickNo + "]");
try {
// 吃不到东西,哭了
guiMgr.setPersonCrying(id);
chopsticks[chopstickNo].wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 获取得到筷子了
guiMgr.setChopstickImage(id, chopstickNo);
// 占用筷子
chopsticks[chopstickNo].setUsed(true);
System.out.println("Person[" + id + "] has got Chopstick["
+ chopstickNo + "] at time:" + new Date());
}
请先忽略所有与guiMgr相关的代码,那是为此程序加上GUI界面的代码,与逻辑没有任何关系。
首先某个特定的筷子是竞争资源,只有它未被占用时才可持有,也就是上面的while语句。如果一直被Used,则一直wait。直到被notifyAll则可继续下面的代码,即把筷子占用chopsticks[chopstickNo].setUsed(true);
接下来是吃的动作
/**
* 吃几秒钟.
*/
private void eating() {
System.out.println("Person[" + id + "] is eating using Chopsticks["
+ leftNo + "][" + rightNo + "]");
try {
// 得到了两只筷子,可以吃东西了
guiMgr.setPersonEating(id);
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 吃完释放左右筷子
synchronized (chopsticks[leftNo]) {
// 释放筷子
guiMgr.releaseChopstick(leftNo);
chopsticks[leftNo].setUsed(false);
// 唤醒其他需要此筷子的哲学家
chopsticks[leftNo].notifyAll();
}
synchronized (chopsticks[rightNo]) {
// 释放筷子
guiMgr.releaseChopstick(rightNo);
chopsticks[rightNo].setUsed(false);
chopsticks[rightNo].notifyAll();
}
}
吃饭大概几秒钟时间,然后把左右筷子释放,并唤醒等待该筷子的其他哲学家。至此,哲学家问题就搞定了。接下来看看GUI图像实现部分。
本例采用了组合的方式即PhilosopherDining中含有GuiManage的引用。GuiManage是专门用于转换哲学家问题到GUI图像显示的中间类,它又含有图像实现类JFrameGridLayout的引用,根据PhilosopherDining中Person实例传递的参数做判断,调用JFrameGridLayout暴露出的方法来控制GUI界面。从而剥离了业务逻辑与前端显示的耦合。
大体思路如下。我使用的是GridLayout,即网格布局。
布局的各组件如下。其中最外层是数字,表示坐标,灰色部分才是真正的布局区域,P表示哲学家,C表示筷子。
P0~P4分布在五个相对对称的位置;
筷子的位置因为会经常变化,所以使用复合数字及不同底色表示。其中
红色表示筷子的初始位置,即每个筷子未被任何人持有的情况下的位置;
蓝色表示筷子相对于其初始位置的右边;
黄色表示筷子相对于其初始位置的左边;
这时再看GuiManage中的下面常量就好理解了
/**
* 人的位置,固定的.
*/
private static final Position[] personPositions = new Position[MAX];
static {
personPositions[0] = new Position(0, 5);
personPositions[1] = new Position(4, 10);
personPositions[2] = new Position(9, 9);
personPositions[3] = new Position(9, 1);
personPositions[4] = new Position(4, 0);
}
/**
* 筷子位置,枚举了筷子三种状态的位置.
*/
private static final ChopstickPosition[] chopstickPositions = new ChopstickPosition[MAX];
static {
// 枚举五个筷子的十五种状态下对应的位置
chopstickPositions[0] = new ChopstickPosition(new int[][] { { 1, 6 }, { 3, 9 }, { 3, 7 } });
chopstickPositions[1] = new ChopstickPosition(new int[][] { { 5, 9 }, { 8, 9 }, { 6, 7 } });
chopstickPositions[2] = new ChopstickPosition(new int[][] { { 9, 8 }, { 9, 2 }, { 7, 5 } });
chopstickPositions[3] = new ChopstickPosition(new int[][] { { 8, 1 }, { 5, 1 }, { 6, 3 } });
chopstickPositions[4] = new ChopstickPosition(new int[][] { { 3, 1 }, { 1, 4 }, { 3, 3 } });
还有这段
/**
* 根据筷子号码,及被谁持有设置筷子图片.
*
* @param id
* 人的id
* @param no
* 筷子号码
*/
public void setChopstickImage(final int id, final int no) {
Position p = null;
String image = null;
if (id % MAX == no) {
p = chopstickPositions[no].getPosition(RIGHT_INDEX);
image = getChopstickImage(no, RIGHT_INDEX);
} else {
p = chopstickPositions[no].getPosition(LEFT_INDEX);
image = getChopstickImage(no, LEFT_INDEX);
}
setChopstickImage(no, p, image);
}
id % MAX == no则是相对于筷子原位置的右边,这是很好理解的。如现在是哲学家0持有0号筷子则0 % 5 == 0是成立的,则0号筷子处于C00 即(1,6)的位置,(1,6)则为chopstickPositions[0]的第0(RIGHT_INDEX的值)个元素,反正则是C01,这则是chopstickPositions[0]的第1(LEFT_INDEX的值)个元素。
理解了此处则整个画图程序也就基本理解了。
注:C00中第一个数字表示第0号筷子,第二个数字表示是RIGHT_INDEX、LEFT_INDEX或INIT_INDEX的其中一个值。
- 大小: 37.5 KB
- 大小: 36 KB
- 大小: 36.9 KB
- 大小: 29.6 KB
- 大小: 24.9 KB
分享到:
相关推荐
### JAVA实现哲学家就餐问题详解 #### 背景与问题描述 哲学家就餐问题是一个经典的并发编程问题,由Edsger Dijkstra提出,用于演示死锁、饥饿、竞态条件等多线程同步问题。场景设定为五位哲学家围坐在圆桌旁,桌上...
在这个C++图形界面实现的项目中,我们能够观察到哲学家如何并发地尝试获取筷子并避免死锁的发生。项目的核心在于设计和实现一个有效的同步机制来确保资源(筷子)的公平分配和释放。常见的解决方案包括使用信号量、...
为了增加可理解性,这个程序还包括了可视化动态展示,这意味着用户可以通过图形界面观察哲学家拿取和放下筷子的过程。这通常涉及到Windows Forms或WPF技术,创建窗口和控件来模拟餐桌和筷子。通过动画效果,可以直观...
图形化界面使得问题的演示更加直观,帮助理解多线程环境下的并发行为。 此外,解决哲学家就餐问题还可以采用银行家算法、避免死锁的条件等策略。银行家算法是一种预防死锁的方法,通过预先分配资源并进行安全性检查...
在这个实验中,通过三种不同的方法实现了哲学家进餐问题的解决方案,这些方法可能包括: 1. **避免死锁的方法**:一种常见的解决策略是使用“先来先服务”(FCFS)规则,即哲学家只能先请求他们发现的第一个空闲...
在"philosopher"这个文件夹中,可能包含了一个Java项目,该项目实现了上述的哲学家问题解决方案。项目可能包括以下组件: 1. `Philosopher`类:代表一个哲学家线程,包含获取和释放筷子的逻辑。 2. `Fork`类:表示...
《MFC哲学家就餐问题》的课程设计是操作系统学习中的一个重要实践环节,它主要涉及到多线程编程、同步机制以及资源管理等核心概念。在这个问题中,哲学家们围坐在一张圆桌旁,每人面前有一根筷子,总共五根。当一个...
总的来说,"哲学家就餐问题GUI"是一个结合了并发控制理论与实践的项目,它通过C++和EasyX库展示了如何用图形化方式模拟并发问题,并通过超时策略避免死锁,对于理解和解决实际系统中的并发问题具有一定的教学价值。
在Java实现的图形界面版本中,通常会包含一个主窗口,显示哲学家和筷子的可视化表示,以及控制线程执行的按钮。用户可以观察到哲学家的思考、拿取筷子和进食过程,直观地理解问题和解决方案。图形界面的实现可能涉及...
进程同步互斥——不死锁哲学家问题 java实现。计算机系统原理,课程设计,(1)利用进程并发执行原理,采用奇数号哲学家先拿左叉子,偶数号哲学...(2)利用java中Swing技术将哲学家就餐的活动过程用可视图形化界面表示出来
在这个课程设计中,MFC被用来创建一个图形化的界面,使用户可以直观地观察哲学家就餐问题的模拟过程。 在解决哲学家就餐问题时,有几种常见的解决方案: 1. **固定顺序策略**:设定一个固定的拿放筷子的顺序,例如...
《哲学家就餐问题的C++实现与图形界面解析》 在计算机科学中,"哲学家就餐问题"(Dining Philosophers Problem)是一个经典的多线程并发控制问题,由Edsger Dijkstra在1965年提出。这个问题旨在模拟五个哲学家围坐...
4. **图形化界面**:可能通过GUI(图形用户界面)显示哲学家的状态,例如思考、吃饭或等待筷子,这有助于理解程序的运行过程。 5. **测试与调试**:文档可能包含了对代码的测试用例和调试过程,以验证其正确性和效率...
可以使用C#的WinForms或WPF库创建图形界面,展示哲学家的动作和筷子的状态,增加可读性和趣味性。 通过这个项目,学习者不仅可以深入理解操作系统中的并发控制和死锁问题,还能锻炼C#编程能力,特别是线程同步和...
5. **图形化界面**:描述中提到的“图形版”,意味着代码可能包含了一个用户界面,使得用户能够直观地观察哲学家的行为,这通常会用到Java的Swing或JavaFX库来实现。 6. **设计模式**:在解决此类问题时,可能会...
总的来说,这个实现通过编程解决了哲学家问题,避免了死锁的发生,同时提供了图形化的用户界面来展示进程。这为我们提供了一个理解多线程同步和死锁避免机制的实例,对于学习并发编程和系统设计具有重要意义。通过...
哲学家进餐问题(Dining Philosophers Problem)是计算机科学中的一个经典同步问题,由艾兹格·迪科斯彻提出,旨在探讨并发系统中避免资源饥饿和死锁的方法。在这个问题中,假设五个哲学家围坐在一张圆桌旁,每人...
在"哲学家就餐问题"的Java实现中,我们通常会创建一个代表筷子的类,并将每个筷子作为对象锁定。当哲学家尝试拿起筷子时,如果筷子已被其他哲学家持有,那么该哲学家将被置于等待状态,直到筷子释放。 `java_...
《Java GUI实现的进餐哲学家问题与信号量解决死锁》 在计算机科学中,进餐哲学家问题(Dining Philosophers Problem)是一个经典的多线程同步问题,由艾兹格·迪科斯彻提出,用于模拟并发系统中可能出现的资源竞争...