哲学家问题(图形化实现)

记得大二时学操作系统,有个关于锁的经典模型——哲学家模型。当时老师要求我们用程序实现,我当时没做出来,一直耿耿于怀。今天看Java cookbook中的线程介绍。于是动手来试下,花了一天才弄出来。在此把大概过程罗列如下,也算是交几年前的作业吧。

 

程序的设计分两部分:哲学家问题的逻辑处理、图形化展示结果部分。最终效果如下:

0

1:初始时五个哲学家都在睡眠

1

2::哲学家03在睡眠,14在吃饭,2挨饿

3:哲学家03在吃饭,1在挨饿,24在睡眠

首先做一点约定:

对于哲学家按顺时针计算,最顶部的哲学家为0,右边的为1,右下角为2……。

对于筷子见图1,哲学家01夹着的筷子为0,按顺时针递增,即12中间的筷子为1……

 

至于什么是哲学家问题,若忘记了可以翻阅下操作系统温习下,简单的描述下:有五个哲学家要吃饭,同时只有五个筷子,为了吃饭必须同时获得两个筷子。如果每个哲学家都是先拿左边筷子再拿右边筷子,则必然发生死锁。所以规定了奇数的先拿左边,偶数的先拿右边。由此可见,筷子是竞争资源。准确的讲,

筷子0是哲学家01竞争的资源;

筷子1是哲学家12竞争的资源;

筷子2是哲学家23竞争的资源;

筷子3是哲学家34竞争的资源;

筷子4是哲学家40竞争的资源;

 

类图如下:

所有的逻辑都在Person中完成。Person是一个继承了Thread的线程。在PhilosopherDiningmain方法中实例化了五个Personstart了他们。现在我们看下Personrun方法及其涉及的其他方法:

/**
* 不断的睡眠、吃饭.
* 
* @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_INDEXLEFT_INDEXINIT_INDEX的其中一个值。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值