死锁—哲学家吃饭问题

    由于线程能被阻塞,更由于synchronized方法能阻止其它线程访问本对象,因此有可能会出现如下这种情况:线程一在等线程二(释放某个对象),线程二又在等线程三,这样依次排下去直到有个线程在等线程一。这样就形成了一个环,每个线程都在等对方释放资源,而它们谁都不能运行。这就是所谓的死锁(deadlock)。

如果程序一运行就死锁,那倒也简单了。你可以马上着手解决这个问题。但真正的麻烦在于,程序看上去能正常运行,但是却潜伏着会引起死锁的隐患。或许你认为这里根本就不可能会有死锁,而bug也就这样潜伏下来了。直到有一天,让某个用户给撞上了(而且这种bug还很可能是不可重复的)。所以对并发编程来说,防止死锁是设计阶段的一个重要任务。

    下面我们来看看由Dijkstra发现的经典的死锁场景:哲学家吃饭问题。原版的故事里有五个哲学家(不过我们的例程里允许有任意数量)。这些哲学家们只做两件事,思考和吃饭。他们思考的时候,不需要任何共享资源,但是吃饭的时候,就必须坐到餐桌旁。餐桌上的餐具是有限的。原版的故事里,餐具是叉子,吃饭的时候要用两把叉子把面条从碗里捞出来。但是很明显,把叉子换成筷子会更合理,所以:一个哲学家需要两根筷子才能吃饭。

    现在引入问题的关键:这些哲学家很穷,只买得起五根筷子。他们坐成一圈,两个人的中间放一根筷子。哲学家吃饭的时候必须同时得到左手边和右手边的筷子。如果他身边的任何一位正在使用筷子,那他只有等着。

    这个问题之所以有趣就在于,它演示了这么一个程序,它看上去似乎能正常运行,但是却容易引起死锁。你可以自己试试,用命令行参数调节哲学家的数量和思考的时间。如果有很多哲学家,而且/或者他们思考的时间很长,或许你永远也碰不到死锁,但是死锁的可能性总还是在的。默认的命令行参数会让它很快地死锁:

<strong><span style="font-family:KaiTi_GB2312;font-size:18px;"><strong>//: c13:DiningPhilosophers.java
// Demonstrates how deadlock can be hidden in a program.
// {Args: 5 0 deadlock 4}
import java.util.*;
class Chopstick {
  private static int counter = 0;
  private int number = counter++;
  public String toString() {
    return "Chopstick " + number;
  }
}
class Philosopher extends Thread {
  private static Random rand = new Random();
  private static int counter = 0;
  private int number = counter++;
  private Chopstick leftChopstick;
  private Chopstick rightChopstick;
  static int ponder = 0; // Package access
  public Philosopher(Chopstick left, Chopstick right) {
    leftChopstick = left;
    rightChopstick = right;
    start();
  }
  public void think() {
    System.out.println(this + " thinking");
    if(ponder > 0)
      try {
        sleep(rand.nextInt(ponder));
      } catch(InterruptedException e) {
        throw new RuntimeException(e);
      }
  }
  public void eat() {
    synchronized(leftChopstick) {
      System.out.println(this + " has "
        + this.leftChopstick + " Waiting for "
        + this.rightChopstick);
      synchronized(rightChopstick) {
        System.out.println(this + " eating");
      }
    }
  }
  public String toString() {
    return "Philosopher " + number;
  }
  public void run() {
    while(true) {
      think();
      eat();
    }
  }
}
public class DiningPhilosophers {
  public static void main(String[] args) {
    if(args.length < 3) {
      System.err.println("usage:\n" +
        "java DiningPhilosophers numberOfPhilosophers " +
        "ponderFactor deadlock timeout\n" +
        "A nonzero ponderFactor will generate a random " +
        "sleep time during think().\n" +
        "If deadlock is not the string " +
        "'deadlock', the program will not deadlock.\n" +
        "A nonzero timeout will stop the program after " +
        "that number of seconds.");
      System.exit(1);
    }
    Philosopher[] philosopher =
      new Philosopher[Integer.parseInt(args[0])];
    Philosopher.ponder = Integer.parseInt(args[1]);
    Chopstick
      left = new Chopstick(),
      right = new Chopstick(),
      first = left;
    int i = 0;
    while(i < philosopher.length - 1) {
      philosopher[i++] =
        new Philosopher(left, right);
      left = right;
      right = new Chopstick();
    }
    if(args[2].equals("deadlock"))
      philosopher[i] = new Philosopher(left, first);
    else // Swapping values prevents deadlock:
      philosopher[i] = new Philosopher(first, left);
    // Optionally break out of program:
    if(args.length >= 4) {
      int delay = Integer.parseInt(args[3]);
      if(delay != 0)
        new Timeout(delay * 1000, "Timed out");
    }
  }
} ///:~
</strong></span></strong>

    Chopstick和Philosopher都包含一个能自动递增的static counter。每个Philosopher有两个reference,一个表示左边那根Chopstick,另一个表示右边那根;Philosopher吃饭之前必须拿到这两根筷子。

    static的ponder表示哲学家要花多长时间思考。如果这个值非零,则think( )会用它来生成一个随机的休眠时间。我们用这种方法证明,如果线程(也就是哲学家)花在其它事情上(思考)的时间多了,那么它们使用共享资源(筷子)的机会就少了,因而程序就不太容易死锁了,但事实并非如此。请看eat( )。Philosopher先synchronized左边那根筷子。如果得不到,他就等,这时他处于阻塞状态。得到左边那根筷子之后,他又用相同的方法去申请右边的筷子。吃完之后,他也是先放左边的,再放右边的。Philosopher在run()里不停的思考和吃饭。


    最近看马士兵的j2se中,讲到死锁那里的时候,马士兵老师讲到了五个哲学家吃饭谁都不肯舍弃自己的筷子,最终饿死的事情,其实就是一个死锁的问题,就是你不能访问别人,别人也不能访问你,你把自己锁在了自己的一个小圈子里。可以看出锁有好处,就是一旦上锁,在锁释放之前,没有其他人可以访问,从而保证了安全,但是生活中的我们不能为自己的生活加锁,看似保护了自己,实际上是与他人的距离在逐渐加大,保护私有物品,我们可以上锁,但是生活中的我们还是要向外开放的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值