Java 中的哲学家就餐问题及解决方案

2025 年 9 月 4 日 | 阅读 6 分钟

哲学家就餐问题是关于竞争进程之间有限资源分配的并发问题的一个例子。在本节中,我们将了解 如何在哲学家就餐问题中避免死锁。这是并发系统中一个不希望出现的情况。它表现为循环等待状态。首先,我们将讨论操作系统中使用的哲学家就餐问题,然后我们将转向解决方案。此外,我们还将用 Java 程序实现解决方案。

哲学家就餐问题

问题的图示如下。

Dining Philosophers Problem and Solution in Java

上图表示有五位哲学家(标记为 P1、P2、P3、P4 和 P5)围坐在一张圆形的餐桌旁。有五盘面条供哲学家感到饥饿时食用。为了吃面条,每位哲学家之间放置着五副叉子/筷子(标记为 1 到 5)。

每位哲学家轮流吃饭和思考。每位哲学家都遵循以下条件:

  • 哲学家将同时使用左手和右手的叉子/筷子来吃饭。
  • 剩下的一副叉子/筷子可以由其任何一个相邻的哲学家拾取,但不能同时拾取两副。
  • 当两副叉子/筷子都可用时,哲学家可以吃面条。
  • 吃完后,哲学家会放下两副叉子/筷子,然后继续思考。
  • 这些叉子/筷子可以被其他哲学家使用,他们将重复相同的过程。
  • 任何两位相邻的哲学家(左边和右边)不能同时吃饭。

最初,所有哲学家都在思考。过了一会儿,他们感到饥饿并想吃面条。哲学家会查看两边的叉子/筷子。当哲学家拿到两副叉子/筷子时,他开始吃饭。吃完后,他放下叉子/筷子,然后开始思考。当哲学家放下叉子/筷子时,相邻的哲学家可能会使用它们。

在这种情况下,可能会发生 死锁,即两个或多个进程无法继续执行的状态。该问题用于设计一种调度技术,以确保没有哲学家会挨饿。

哲学家就餐问题的解决方案

哲学家就餐问题的解决方案是使用 信号量 (Semaphore)。它是用于并发进程的工具。使用信号量作为解决方案存在一个缺点。它可能导致死锁。假设一种情况是所有哲学家都先拿起左手的叉子/筷子,然后等待右手边的叉子/筷子。这种情况会导致死锁。

为了避免死锁,应采取一些措施:

  • 桌子周围最多应有四位哲学家。
  • 当两副叉子/筷子同时可用时,应允许哲学家拾取。
  • 哲学家可以轮流吃饭和思考。例如,如果第一位哲学家正在吃饭,那么相邻的哲学家应该等待和思考,依此类推。

哲学家就餐问题的实现

在下面的程序中,我们首先初始化哲学家的数量(5)。两个数组 philosophers[]chopsticks[] 都用哲学家数量(5)进行初始化。

为了实现筷子的逻辑,我们创建了一个名为 Chopstick 的类。在该类中,我们创建了 Semaphore 类的构造函数,并定义了三个方法:grab()、release()isFree()

grab() 方法调用 acquire() 方法,该方法从信号量获取一个许可。它会将许可数量减 1。如果没有可用的许可,则当前线程将禁用。

用户定义的 release() 方法调用 Semaphore 类的 release() 方法。它释放指定数量的许可,并将许可数量加 1

isFree() 方法检查信号量中许可的可用性。它调用 Semaphore 类的 availablePermits() 方法,该方法返回信号量中可用的许可数量。

之后,我们创建了另一个名为 Philosopher 的类,该类扩展了 Thread 类。在该类中,我们定义了三个变量:number、leftchopstickrightchopstick。此外,我们还创建了 Philosopher 类的构造函数eat() 方法。如果线程是使用单独的 Runnable 对象构造的,则会调用 Thread 类的 run() 方法。

当哲学家同时拥有左右两副筷子时,将执行 run() 方法。哲学家通过调用 eat() 方法开始吃饭,并持有叉子/筷子一段时间(睡眠时间)。为了确定睡眠时间,我们使用了 ThreadLocalRandomcurrent()nextInt() 方法。该方法返回一个介于01000之间的伪随机、均匀分布的整数值。该值由传递给 sleep() 方法的 nextInt() 方法确定,该方法会将线程睡眠指定的时长。在本例中,如果睡眠时间超过 1000 毫秒,程序将退出。因此,我们可以设置伪随机数的范围(睡眠时间)。

main() 方法内部,我们定义了两个 for 循环:一个用于筷子,另一个用于哲学家。之后,我们检查死锁条件。如果发生死锁,则意味着每位哲学家都在通过获取筷子/叉子来吃饭。程序执行中断。只要资源(一根筷子)可用,死锁条件就不会发生。

DiningPhilosophersProblem.java

输出

Dining Philosophers Problem and Solution in Java

我们对上面的程序做一些小的改动。

在 while 循环内,将线程睡眠时间从1000更改为2000 毫秒

现在执行上面的程序,我们将得到以下输出。

Dining Philosophers Problem and Solution in Java

注意:您可能会得到与上述输出不同的输出。因为每次执行程序时输出都会发生变化。