当前位置: 首页 > 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]);
      
    • 实现简单,保证不会形成循环等待链

http://www.xdnf.cn/news/350965.html

相关文章:

  • 数字信号处理|| 离散序列的基本运算
  • IPv6协议
  • 基于Transformer与SHAP可解释性分析的神经网络回归预测模型【MATLAB】
  • 英文单词 do、play、go 的区别
  • 大模型的RAG技术系列(二)
  • ADV7842KBCZ - 5 富利威长期稳定供应
  • MLX-Audio:高效音频合成的新时代利器
  • 【图片识别内容改名】图片指定区域OCR识别并自动重命名,批量提取图片指定内容并重命名,基于WPF和阿里云OCR识别的解决
  • wpf UserControl 更换 自定义基类
  • 三款实用电脑工具
  • 【CTFSHOW_Web入门】命令执行
  • K8S - GitLab CI 自动化构建镜像入门
  • 按位宽提取十六进制值
  • OpenCV的 ccalib 模块用于自定义标定板的检测和处理类cv::ccalib::CustomPattern()----函数calibrate
  • uniapp开发的项目上传到国内主流应用市场(华为、小米、oppo、vivo)
  • COLT_CMDB_aix_diskinfo.sh
  • OCCT中的基础变换
  • C++卡特兰数讲解
  • Java 显式锁与 Condition 的使用详解
  • Android MVC架构的现代化改造:构建清晰单向数据流
  • AI搜索的未来:技术纵深发展与关键突破路径
  • Kubernetes 手动部署 Prometheus 学习计划
  • 【计算机网路】--tcp四次挥手关闭连接
  • pm2 list查询服务时如何通过name或者namespace进行区分
  • 文本文件的定义
  • CTF杂项入门(BUUCTF-Misc第一页)
  • Python机器学习中的字典列表特征提取
  • 基于vue3+QuillEditor的深度定制
  • [数据库之十四] 数据库索引之位图索引
  • 最短路径-Dijkstra及其堆优化版本