哲学家就餐问题并发解决方案失效,请求排查问题原因
五个沉默的哲学家围坐在圆桌旁,每两个相邻哲学家之间放置一只叉子。每个哲学家必须交替进行思考和进食,仅当同时持有左右两只叉子时才能进食,每只叉子同一时间只能被一位哲学家持有,进食完成后需放下两只叉子以便他人使用。需设计无饥饿的并发算法,确保每位哲学家能持续交替思考与进食(假设哲学家无法知晓他人的进食/思考时机)。
哲学家ID按顺时针顺序编号为0至4,需实现void wantsToEat(philosopher, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork)方法,参数说明如下:
- philosopher:请求进食的哲学家ID;
- pickLeftFork、pickRightFork:用于拿起对应叉子的Runnable方法;
- eat:用于让哲学家进食的Runnable方法;
- putLeftFork、putRightFork:用于放下对应叉子的Runnable方法;
- 未调用该方法时,哲学家处于思考状态;五个线程分别代表一位哲学家,将同时使用该类的一个实例模拟过程,同一哲学家的该方法可能被多次调用(即使上一次调用未结束)。
我实现了两个synchronized方法pickForks和putDownForks,分别用于获取和释放叉子。wantsToEat方法会循环等待直到成功获取两只叉子,之后依次执行拿起左叉、拿起右叉、进食、放下左叉、放下右叉的操作,最后调用putDownForks更新叉子状态。
我认为此方案可避免死锁,因为不会出现持有一只叉子等待另一只的情况,不存在“Hold and wait”条件。同时我认为synchronized方法具备以下特性:
- 两个线程无法同时执行同一个
synchronized方法; - 方法具备atomic性,执行该方法的线程会完整执行完方法后,其他线程才能开始执行,且对
boolean数组的更新对其他线程可见。
但我的代码在部分测试用例中失败,无法确定并发执行过程中出现问题的原因,请求帮助排查。
问题排查与分析
1. pickForks的阻塞逻辑可能导致锁独占
如果你的pickForks方法是在synchronized块内循环等待叉子(而非直接检查状态后返回),会直接导致对象锁被长期占用——其他线程连尝试获取叉子或释放叉子的机会都没有,最终引发系统僵死或超时。
正确的pickForks应该是非阻塞检查:若左右叉子均空闲,标记为占用并返回true;否则返回false,让wantsToEat的循环在锁外重试。如果需要等待,必须用wait()释放锁,避免锁独占,示例如下:
synchronized void pickForks(int philosopher) throws InterruptedException { int left = philosopher; int right = (philosopher + 1) % 5; // 拿不到叉子就释放锁等待,避免占用锁循环 while (forks[left] || forks[right]) { wait(); } forks[left] = true; forks[right] = true; }
2. 叉子编号的对应关系可能错误
要确认哲学家的左右叉子编号是否正确:比如哲学家i的左叉是i,右叉是(i+1)%5(顺时针顺序)。如果编号搞反,会导致相邻哲学家争抢同一把叉子,引发资源冲突。
3. 未处理同一哲学家的并发调用
题目明确说明同一哲学家的wantsToEat可能被多次调用(即使上一次未结束)。如果你的代码没有给每个哲学家添加“是否正在进食”的标记,会导致同一线程重复尝试拿叉子,破坏叉子状态的正确性——比如第一次调用还拿着叉子进食,第二次调用又去标记叉子为占用,最终导致叉子被永久占用。
4. putDownForks未正确通知等待线程
如果putDownForks仅标记叉子为空闲但未调用notifyAll(),等待中的线程永远不会被唤醒,会一直处于阻塞状态,引发饥饿或死锁。notifyAll()能确保所有等待拿叉子的线程都有机会重新检查叉子状态。
5. eat执行时持有锁导致并发效率极低
如果你的流程是拿着对象锁执行eat方法,会导致锁被长时间占用——其他哲学家无法执行任何拿/放叉子的操作,最终因超时或饥饿导致测试失败。正确的流程应该是:拿到叉子标记后释放锁,再执行pickLeftFork/eat/putLeftFork等操作,最后再获取锁释放叉子。
6. 公平性缺失导致饥饿
你的循环重试逻辑可能导致某些哲学家永远抢不到叉子(比如被其他线程持续抢先)。要解决饥饿问题,可以引入公平锁(比如ReentrantLock(true))或请求排队机制,确保每个哲学家的进食请求都能被公平处理。
内容的提问来源于stack exchange,提问作者statviks

