当前位置: 首页 > news >正文

用 Java 实现 哲学家就餐问题

用 Java 实现 哲学家就餐问题

这篇文章将分析死锁产生的条件,并使用 Java 实现经典的哲学家就餐问题以复现死锁,并给出解开死锁的解决方案。

(一)死锁及其产生条件

死锁是多线程编程中常见的问题,指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象。

死锁产生的条件可以归纳为以下四条。

  • 互斥条件(Mutual Exclusion):资源一次只能由一个线程占用,如果资源可以被共享,就不会产生死锁;
  • 占有并等待(Hold and Wait):线程在持有部分资源的情况下,又申请新的资源,新资源被其他线程持有,导致当前线程阻塞;
  • 非抢占条件(No Preemption):已分配给线程的资源不能被其他线程或操作系统强行夺取,线程持有的资源只能由线程自己释放;
  • 循环等待条件(Circular Wait):形成一个首尾相连的线程与资源的循环等待链。

(二)哲学家就餐问题及死锁复现

哲学家就餐问题是这样的:五位哲学家围坐在圆桌旁,每人左右各有一根筷子。哲学家需要两根筷子才能吃饭。吃饭前、后以及过程中可能进行思考(其实是为了模拟程序中其它代码的运行,因而这里思考时间具有随机性)。

public class DeadLock {public static class Philosopher {private final Chopstick left;private final Chopstick right;public Philosopher(Chopstick left, Chopstick right) {this.left = left;this.right = right;}public void thinkWhileDoAction(String action) throws InterruptedException {System.out.println(Thread.currentThread().getName() + " - " + action);Thread.sleep(new Random().nextInt(1000) + 1000);}public void eat() throws InterruptedException {thinkWhileDoAction("prepare to eat");synchronized (left) {thinkWhileDoAction(String.format("left %d obtained", left.number));synchronized (right) {thinkWhileDoAction(String.format("right %d obtained and eat", right.number));}}}}public static class Chopstick {public int number;}private final Philosopher[] philosophers;public DeadLock() {Chopstick[] chopsticks = new Chopstick[5];for (int i = 0; i < 5; i++) {chopsticks[i] = new Chopstick();chopsticks[i].number = i;}philosophers = new Philosopher[5];philosophers[0] = new Philosopher(chopsticks[0], chopsticks[1]);philosophers[1] = new Philosopher(chopsticks[1], chopsticks[2]);philosophers[2] = new Philosopher(chopsticks[2], chopsticks[3]);philosophers[3] = new Philosopher(chopsticks[3], chopsticks[4]);philosophers[4] = new Philosopher(chopsticks[4], chopsticks[0]);}public void eat() {for (Philosopher philosopher : philosophers) {new Thread(() -> {try {philosopher.eat();} catch (InterruptedException e) {throw new RuntimeException(e);}}).start();}}public static void main(String[] args) {DeadLock deadLock = new DeadLock();deadLock.eat();}}

通过上面的代码,我们成功的复现了死锁的发生:

Thread-1 - prepare to eat
Thread-5 - prepare to eat
Thread-4 - prepare to eat
Thread-3 - prepare to eat
Thread-2 - prepare to eat
Thread-4 - left 3 obtained
Thread-3 - left 2 obtained
Thread-2 - left 1 obtained
Thread-1 - left 0 obtained
Thread-5 - left 4 obtained

程序最终卡住,不进行任何输出。

(三)哲学家就餐问题的解决

上面提到,死锁发生的条件有四个,因此我们只需要破坏其中一个就可以了。

  1. 互斥条件的破坏(不推荐)

    • 这种方法下,我们让筷子可以被共享(如使用读写锁,允许多个哲学家同时使用同一根筷子)

    • 这与就餐问题的基本设定冲突,因为筷子必须独占使用才能进餐

  2. 占有并等待的破坏

    • 让哲学家一次性获取两根筷子,否则不获取任何筷子

      public void eat() throws InterruptedException {thinkWhileDoAction("prepare to eat");synchronized (left) {synchronized (right) {thinkWhileDoAction(String.format("both chopsticks %d and %d obtained and eat", left.number, right.number));}}
      }
      
    • 可能导致饥饿(某些哲学家可能永远无法同时获取两根筷子)

  3. 非抢占条件的破坏

    • 使用tryLock()等可中断的锁机制

      public static class Chopstick {public int number;public Lock lock = new ReentrantLock();
      }public void eat() throws InterruptedException {thinkWhileDoAction("prepare to eat");while (true) {if (left.lock.tryLock()) {try {if (right.lock.tryLock()) {try {thinkWhileDoAction(String.format("both chopsticks %d and %d obtained and eat", left.number, right.number));return;} finally {right.lock.unlock();}}} finally {left.lock.unlock();}}}
      }
      
    • 避免死锁,更接近现实情况;实现复杂,可能降低性能

  4. 循环等待条件的破坏(这是最简单的方式!)

    • 让最后一位哲学家改变拿筷子的顺序即可

      philosophers[4] = new Philosopher(chopsticks[0], chopsticks[4]);
      
    • 实现简单,保证不会形成循环等待链

相关文章:

  • LeetCode百题刷002摩尔投票法
  • 卡洛诗的“破”与“立”
  • 【el-admin】el-admin快速上手,新增图书管理模块
  • ZYNQ笔记(十九):VDMA VGA 输出分辨率可调
  • linux中的日志分割
  • 安达发|人力、机器、物料——APS排程软件如何实现资源最优配置?
  • 期货跟单软件如何对实盘进行风控?
  • awesome-digital-human本地部署及配置:打造高情绪价值互动指南
  • Shell 脚本编程详细指南:第五章 - 函数与参数传递
  • 边缘大型语言模型综述:设计、执行和应用
  • 麒麟系统使用-个性化设置
  • Arthas工具使用
  • 7:点云处理—眼在手外标定
  • 机器视觉的平板电脑屏幕组件覆膜应用
  • FPGA 41 ,ICMP 协议详细解析之构建网络诊断系统( ICMP 协议与 IP 协议理论详细解析 )
  • Python爬虫抓取Bilibili弹幕并生成词云
  • 全息美AISEO与AIGEO优选榜
  • Oracle Fusion常用表
  • Qt开发:项目视图(Item Views)的介绍和使用
  • ApplicationEventPublisher 深度解析:Spring 事件驱动模型的核心
  • 马上评丨全民定制公交,打开城市出行想象空间
  • 新华时评:直播间里“家人”成“韭菜”,得好好管!
  • 前4个月我国货物贸易进出口同比增长2.4%,增速较一季度加快1.1个百分点
  • 独家丨刘家琨获普利兹克奖感言:守护原始的感悟力
  • 招行:拟出资150亿元全资发起设立金融资产投资公司
  • 外交部回应西班牙未来外交战略:愿与之一道继续深化开放合作