1226. The Dining Philosophers (Leetcode 1226)

本文探讨了著名的哲学家就餐问题,通过分析死锁的四个条件,提出了解决方案,包括破坏占有并等待条件、限制同时就餐人数及改变拿筷子顺序等方法,有效避免了死锁现象。

1226. The Dining Philosophers (Leetcode 1226)

题目描述

  1. 原文地址 https://leetcode.com/problems/the-dining-philosophers/

  2. 题目含义在下面代码中:


import java.util.concurrent.Semaphore;

/**
 * 哲学家吃饭问题, 5个人围在一个桌子, 每个人左右两边都有一支筷子, 现在wantsToEat 会被5个线程调用, 怎么让他有序的安排5个哲学家吃饭.
 * <p>
 * 思考: 死锁产生的条件是下面四个,破坏其中任何一个, 死锁都不存在
 *    a. 互斥
 *    b. 不可抢占
 *    c. 占有并且等待.
 *    d. 循环等待
 * <p>
 * 1. 怎么破坏互斥? 有足够多的筷子, 那么每个人都用自己的, 在这里不适用. (NO)
 * 2. 怎么破坏不可抢占 ? 这个不好搞, 一个哲学家拿到一支筷子, 别的哲学家不能抢占(从手中抢过来) (NO)
 * 3. 怎么破坏占有并且等待 ?  要么拿不到筷子,要么一次性拿到, 这样不就存在咱有并且等待的问题. (OK)
 * 4. 怎么破坏循环等待 ?
 *    发生循环等待必定是(假如每个人都是先左后右) 每个人拿到了自己的左边, 同时在等待另一边,
 *    a. 如果只让最多4个人吃饭, 那么这个循环等待的问题就不复存在.
 *    b. 如果让0~3 都是先拿左手的筷子 再拿右手的筷子, 而对于4这个人, 先拿右手的筷子, 再拿左手的筷子, 那么这个环路也被破坏了
 *    c. 还有更厉害的人发现 坐标为基数和偶数的人拿筷子的顺序不一样也可以破坏循环等待条件
 */

/*// Solution1 破坏占有并且等待条件 (10ms)
class DiningPhilosophers {
  int n;
  boolean[] availables;
  Object lock;
  public DiningPhilosophers() {
    this(5);
  }
  public DiningPhilosophers(int n) {
    this.n = n;
    // availables 代表的是筷子, 当然了,更好理解的是代表人, 代码需要修改
    this.availables = new boolean[n];
    lock = new Object();
    Arrays.fill(availables, true);
  }

  // call the run() method of any runnable to execute its code
  public void wantsToEat(int philosopher,
                         Runnable pickLeftFork,
                         Runnable pickRightFork,
                         Runnable eat,
                         Runnable putLeftFork,
                         Runnable putRightFork) throws InterruptedException {
    int left = philosopher;
    int right = (philosopher + 1) % n;
    synchronized (lock) {
      while (true) {
        if (!(availables[left] && availables[right])) {
          lock.wait();
          continue;
        }
        availables[left] = availables[right] = false;
        break;
      }
    }
    pickLeftFork.run();
    pickRightFork.run();
    eat.run();
    putLeftFork.run();
    putRightFork.run();
    synchronized (lock) {
      availables[left] = availables[right] = true;
      lock.notifyAll();
    }
  }
}*/

/*// 只允许最多4个人吃饭  20ms
class DiningPhilosophers {
  int n;
  Semaphore eatingPeople;
  Semaphore[] knifes;
  public DiningPhilosophers() {
    this(5);
  }

  public DiningPhilosophers(int n) {
    this.n = n;
    this.eatingPeople = new Semaphore(n - 1);
    knifes = new Semaphore[n];
    for (int i = 0; i < knifes.length; i++) {
      knifes[i] = new Semaphore(1);
    }
  }

  // call the run() method of any runnable to execute its code
  public void wantsToEat(int philosopher,
                         Runnable pickLeftFork,
                         Runnable pickRightFork,
                         Runnable eat,
                         Runnable putLeftFork,
                         Runnable putRightFork) throws InterruptedException {
    eatingPeople.acquire();
    int left = philosopher;
    int right = (philosopher + 1) % n;
    knifes[left].acquire();
    knifes[right].acquire();
    pickLeftFork.run();
    pickRightFork.run();
    eat.run();
    putLeftFork.run();
    putRightFork.run();
    knifes[left].release();
    knifes[right].release();
    eatingPeople.release();
  }
}*/

// 破坏循坏等待,让最后一个人的拿筷子顺序不一样 (10ms)
class DiningPhilosophers {
  int n;
  Semaphore[] semaphores;

  public DiningPhilosophers() {
    this(5);
  }

  public DiningPhilosophers(int n) {
    this.n = n;
    this.semaphores = new Semaphore[n];
    for (int i = 0; i < n; i++) {
      semaphores[i] = new Semaphore(1);
    }
  }

  // call the run() method of any runnable to execute its code
  public void wantsToEat(int philosopher,
                         Runnable pickLeftFork,
                         Runnable pickRightFork,
                         Runnable eat,
                         Runnable putLeftFork,
                         Runnable putRightFork) throws InterruptedException {
    int left = philosopher;
    int right = (philosopher + 1) % n;
    // if the guy is the last one, change its order to pick the chopsticks
    if (left == n - 1) {
      left = 0;
      right = n - 1;
    }
    semaphores[left].acquire();
    semaphores[right].acquire();
    pickLeftFork.run();
    pickRightFork.run();
    eat.run();
    putLeftFork.run();
    putRightFork.run();
    semaphores[left].release();
    semaphores[right].release();
  }
}

### Dining Philosophers Problem 概述 Dining Philosophers Problem 是由 Edsger Dijkstra 提出的经典同步问题,用于描述并发编程中的资源竞争和死锁现象[^1]。该问题设定如下: 五个哲学家围坐在一张圆桌旁,每位之间放置一根筷子。为了吃饭,每名哲学家需要同时拿起左边和右边的两根筷子。 ### 实现方式与挑战 主要挑战在于如何设计算法来防止死锁的发生,即避免所有哲学家都只拿到一根筷子而陷入无限等待的状态。常见的解决方法有多种变体,包括但不限于: #### 方法一:奇偶策略 通过规定只有当编号为奇数的哲学家先拿左侧筷子,偶数则相反的方式减少冲突的可能性。 ```python import threading class Chopstick: def __init__(self, index): self.index = index self.lock = threading.Lock() def philosopher(index, left_chopstick, right_chopstick): while True: if index % 2 == 0: with left_chopstick.lock: with right_chopstick.lock: eat() else: with right_chopstick.lock: with left_chopstick.lock: eat() ``` #### 方法二:引入仲裁者 设置一个额外的服务进程作为监督者,在分配筷子前检查是否有足够的资源可供使用,从而预防潜在的死锁情况发生。 ```python import queue chopsticks_queue = queue.Queue(maxsize=5) for i in range(5): chopsticks_queue.put(i) def supervised_philosopher(id_, supervisor): while True: first = id_ second = (id_ + 1) % 5 supervisor.acquire([first, second]) try: eat() finally: supervisor.release([first, second]) ``` ### 解决方案总结 上述两种方法各有优劣,前者简单易懂但可能造成饥饿;后者虽然能有效避免死锁却增加了系统的复杂度。实际应用中可根据具体需求权衡选择最合适的方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值