哲学家就餐问题用java模拟会因循环等待导致死锁,解决需打破四条件之一:如破坏循环等待(奇偶编号取筷顺序不同)、一次性获取双筷、超时放弃或引入信号量仲裁。

哲学家就餐问题是经典的死锁案例,用 Java 模拟它,关键在于让五个哲学家(线程)按固定顺序竞争五根筷子(共享资源),且各自持有 1 根后等待下一根——这恰好满足死锁四必要条件:互斥、占有并等待、不可剥夺、循环等待。
用 synchronized 模拟死锁
每个筷子用一个 Object 表示,每位哲学家尝试先拿左边筷子、再拿右边筷子。若所有哲学家同时执行,就可能全部卡在“拿到左筷 → 等右筷”阶段:
- 定义 Chopstick 类(仅作锁对象,无需方法)
- 定义 Philosopher 线程类,在
run()中按固定顺序(如left,right)调用synchronized(chopstick) { ... } - 启动 5 个哲学家线程,不加任何干预——大概率几秒内程序挂起,无输出、无退出,即发生死锁
四种主流解决策略及 Java 实现要点
核心是打破死锁四条件之一,常用方式有:
-
破坏循环等待(推荐):规定奇数号哲学家先拿左筷、再拿右筷;偶数号则相反(先右后左)。这样至少一人能打破闭环。Java 中只需在
run()内加if (id % 2 == 0) { pick(right); pick(left); } else { pick(left); pick(right); } -
破坏占有并等待:要求哲学家必须一次性获取两根筷子才开始吃饭。可用
ReentrantLock.tryLock(timeout, TimeUnit)尝试获取两把锁,失败则释放已持锁并重试,避免长期持有单锁等待 -
超时放弃(简单实用):每根筷子加
tryLock(100, TimeUnit.MILLISECONDS),拿不到就释放已持资源,休眠后重试。虽不保证公平,但彻底避免死锁 -
引入仲裁者(如 waiter):用一个全局锁或信号量控制最多 4 人同时进餐,确保至少一人能拿到双筷。例如用
Semaphore diningRoom = new Semaphore(4);,进入前acquire(),离开后release()
验证是否真解除了死锁
不能只看程序跑通,要主动探测:
- 运行时用
jstack <pid></pid>查看线程堆栈,确认无java.lang.Thread.State: BLOCKED (on object monitor)的环形等待链 - 在哲学家进食逻辑中加入日志,例如
System.out.printf("%d eats %d times%n", id, eatCount);,观察各线程是否持续交替执行,而非某几个长期沉默 - 压力测试:延长思考/进食时间、增加运行时长(如 30 秒),确保稳定性
注意事项与常见误区
实际编码时容易踩坑:
- 别用
wait()/notify()手动管理筷子状态——易出错且不解决根本问题;优先用显式锁或语义清晰的同步机制 - 所有筷子对象必须是同一个实例(如 static 数组),否则 synchronized 锁的是不同对象,根本不会竞争
- 避免在 synchronized 块内调用可能阻塞或抛异常的方法(如 I/O),否则可能提前释放锁导致逻辑错乱
- “随机延迟”不是解决方案:只是降低死锁概率,不能消除——生产环境必须用确定性策略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











