问题描述

哲学家就餐问题(Dining philosophers problem)是在计算机科学中的一个经典问题,用来演示在并发计算中多线程同步时产生的问题。

在1971年,著名的计算机科学家艾兹格·迪科斯彻提出了一个同步问题,即假设有五台计算机都试图访问五份共享的磁带驱动器。稍后,这个问题被托尼·霍尔重新表述为哲学家就餐问题。这个问题可以用来解释死锁和资源耗尽。

哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。

哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。

即使没有死锁,也有可能发生资源耗尽。例如,假设规定当哲学家等待另一只餐叉超过五分钟后就放下自己手里的那一只餐叉,并且再等五分钟后进行下一次尝试。这个策略消除了死锁(系统总会进入到下一个状态),但仍然有可能发生活锁。如果五位哲学家在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉。

在实际的计算机问题中,缺乏餐叉可以类比为缺乏共享资源。一种常用的计算机技术是资源加锁,用来保证在某个时刻,资源只能被一个程序或一段代码访问。当一个程序想要使用的资源已经被另一个程序锁定,它就等待资源解锁。当多个程序涉及到加锁的资源时,在某些情况下就有可能发生死锁。例如,某个程序需要访问两个文件,当两个这样的程序各锁了一个文件,那它们都在等待对方解锁另一个文件,而这永远不会发生。

死锁的必要条件

死锁的产生具备以下四个条件:

  • 互斥条件:指线程对己经获取到的资源进行排它性使用, 即该资源同时只由一个线程占用。如果此时还有其他线程请求获取该资源,则请求者只能等待,直至占有资源的线程释放该资源。
  • 请求并持有条件: 指一个线程己经持有了至少一个资源 , 但又提出了新的资源请求 ,而新资源己被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己己经获取的资源。
  • 不可剥夺条件: 指线程获取到的资源在自己使用完之前不能被其他线程抢占,只有在自己使用完毕后才由自己释放该资源。
  • 环路等待条件:指在发生死锁时,必然存在一个线程→资源的环形链,即线程集合{ T0,T1,T2 ,…,Tn }中的 T0 正在等待一个 T1 占用的资源,T1 正在等待 T2 占用的资源,……Tn 正在等待己被 T0 占用的资源。

复现死锁

当所有哲学家同时决定进餐,拿起左边筷子时候,就发生了死锁。

public class Problem {public static void main(String[] args) {int sum = 5;Philosopher[] philosophers = new Philosopher[sum];Chopstick[] chopsticks = new Chopstick[sum];for (int i = 0; i < sum; i++) {chopsticks[i] = new Chopstick();}for (int i = 0; i < sum; i++) {Chopstick left = chopsticks[i];Chopstick right = chopsticks[(i + 1) % sum];philosophers[i] = new Philosopher(left, right);new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();}}
}class Chopstick {}class Philosopher implements Runnable {private final Chopstick left;private final Chopstick right;public Philosopher(Chopstick left, Chopstick right) {this.left = left;this.right = right;}@Overridepublic void run() {try {while (true) {doAction("思考");synchronized (left) {doAction("拿起左边筷子");synchronized (right) {doAction("拿起右边筷子--------开吃了");doAction("吃完了,放下筷子");}}}} catch (InterruptedException e) {e.printStackTrace();}}private void doAction(String action) throws InterruptedException {System.out.println(Thread.currentThread().getName() + " " + action);TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 10));}
}

解决方法

资源分级算法(破坏死锁的环路等待条件)

资源分级算法是指为资源分配一个偏序或者分级的关系,并约定所有资源都按照这种顺序获取,按相反顺序释放。对应在哲学家就餐问题中就是为各个餐叉设置 1 - 2 - 3 - 4 - 5 的序号,每一个哲学家总是先拿起左右两边编号较低的餐叉,再拿编号较高的。用完餐叉后,他总是先放下编号较高的餐叉,再放下编号较低的。在这种情况下,1 ~ 4 号哲学家都是左边的餐叉序号小,而 5 号哲学家是右边的餐叉序号小,当 1 ~ 4 号哲学家同时拿起他们手边编号较低的餐叉即 1~4 号餐叉时,只有编号最高的 5 号餐叉留在桌上,5 号哲学家先申请序号较小的 1 号,发现已经被拿走,所以他就只能等待。而剩下的那支 5 号餐叉被 4 号哲学家成功获得。当 4 号哲学家吃完后,他会先放下编号最高的餐叉,再放下编号较低的餐叉,从而使得 3 号哲学家成功获得他所需的第二支餐叉,以此类推,整个系统不会发生死锁。实际执行顺序还是要看 CPU 的分配,不过这样已经不会构成循环了。

此处给筷子添加 id,根据 id 从小到大获取(不用关心编号的具体规则,只要保证编号是全局唯一并且有序的)。

代码如下:

public class Solution1 {public static void main(String[] args) {int sum = 5;Philosopher[] philosophers = new Philosopher[sum];Chopstick[] chopsticks = new Chopstick[sum];for (int i = 0; i < sum; i++) {chopsticks[i] = new Chopstick(i);}for (int i = 0; i < sum; i++) {Chopstick left = chopsticks[i];Chopstick right = chopsticks[(i + 1) % sum];philosophers[i] = new Philosopher(left, right);new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();}}
}class Chopstick {private int id;public Chopstick(int id) {this.id = id;}public int getId() {return id;}public void setId(int id) {this.id = id;}}class Philosopher implements Runnable {private final Chopstick left;private final Chopstick right;public Philosopher(Chopstick left, Chopstick right) {if (left.getId() < right.getId()) {this.left = left;this.right = right;} else {this.left = right;this.right = left;}}@Overridepublic void run() {try {while (true) {doAction("思考");synchronized (left) {doAction("拿起左边筷子");synchronized (right) {doAction("拿起右边筷子--------开吃了");doAction("吃完了,放下筷子");}}}} catch (InterruptedException e) {e.printStackTrace();}}private void doAction(String action) throws InterruptedException {System.out.println(Thread.currentThread().getName() + " " + action);TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 10));}
}

破坏死锁的请求并持有条件

1、使用多把锁,每把锁使用 tryLock 为获取锁操作设置超时时间。

代码如下:

public class Solution2 {public static void main(String[] args) {int sum = 5;Philosopher[] philosophers = new Philosopher[sum];ReentrantLock[] chopsticks = new ReentrantLock[sum];for (int i = 0; i < sum; i++) {chopsticks[i] = new ReentrantLock();}for (int i = 0; i < sum; i++) {ReentrantLock left = chopsticks[i];ReentrantLock right = chopsticks[(i + 1) % sum];philosophers[i] = new Philosopher(left, right);new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();}}
}class Philosopher implements Runnable {private final ReentrantLock left;private final ReentrantLock right;public Philosopher(ReentrantLock left, ReentrantLock right) {this.left = left;this.right = right;}@Overridepublic void run() {try {while (true) {doAction("思考");left.lock();try {doAction("拿起左边筷子");if (right.tryLock(10, TimeUnit.MILLISECONDS)) {try {doAction("拿起右边筷子--------开吃了");} finally {right.unlock();doAction("吃完了,放下筷子");}} else {// 没有获取到右手的筷子,放弃并继续思考}} finally {left.unlock();}}} catch (InterruptedException e) {e.printStackTrace();}}private void doAction(String action) throws InterruptedException {System.out.println(Thread.currentThread().getName() + " " + action);TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 10));}
}

2、使用一把锁,设置条件队列 Condition

该方法只用一把锁,没有 Chopstick 类,将竞争从对筷子的争夺转换成了对状态的判断。仅当左右邻座都没有进餐时才可以进餐。

public class Solution3 {public static void main(String[] args) {int sum = 5;Philosopher[] philosophers = new Philosopher[sum];ReentrantLock lock = new ReentrantLock();// 安排哲学家就坐for (int i = 0; i < sum; i++) {philosophers[i] = new Philosopher(lock);}// 设置哲学家的左右邻居for (int i = 0; i < sum; i++) {Philosopher left = philosophers[(i + sum) % sum];Philosopher right = philosophers[(i + 1) % sum];philosophers[i].setLeft(left);philosophers[i].setRight(right);new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();}}
}class Philosopher implements Runnable {private boolean eating;private Philosopher left;private Philosopher right;private final ReentrantLock lock;private final Condition condition;public Philosopher(ReentrantLock lock) {eating = false;this.lock = lock;this.condition = lock.newCondition();}public void setLeft(Philosopher left) {this.left = left;}public void setRight(Philosopher right) {this.right = right;}public void think() throws InterruptedException {lock.lock();try {eating = false;System.out.println(Thread.currentThread().getName() + "开始思考");left.condition.signal();right.condition.signal();} finally {lock.unlock();}Thread.sleep(10);}public void eat() throws InterruptedException {lock.lock();try {// 左右两边只要有任意哲学家在吃饭,就等待while (left.eating || right.eating) {condition.await();}System.out.println(Thread.currentThread().getName() + "开始吃饭");eating = true;} finally {lock.unlock();}Thread.sleep(10);}@Overridepublic void run() {try {while (true) {think();eat();}} catch (InterruptedException e) {}}private void doAction(String action) throws InterruptedException {System.out.println(Thread.currentThread().getName() + " " + action);TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 10));}
}

参考文章https://www.jianshu.com/p/99f10708b1e1

更多解法可以参考leetcode

哲学家就餐问题及解决方法相关推荐

  1. 哲学家就餐问题python解决_关于哲学家就餐问题的分析代码.

    ①总体思路: 都去拿左边的筷子,并且最后一个人不能去拿筷子(防止大家都拿了左边的筷子,没有右边的筷子,导致死锁了),解决死锁问题的办法就是同时只允许四位哲学家同时拿起同一边的筷子,这样就能保证一定会有 ...

  2. 哲学家就餐问题python伪代码_GitHub - doooooit/Dining-philosophers-problem: 哲学家进餐问题的两种解决方法...

    哲学家就餐问题 问题描述 哲学家就餐问题(Dining philosophers problem)可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考.吃东西的时候, ...

  3. Java多线程学习四十二:有哪些解决死锁问题的策略和哲学家就餐问题

    线上发生死锁应该怎么办 如果线上环境发生了死锁,那么其实不良后果就已经造成了,修复死锁的最好时机在于"防患于未然",而不是事后补救.就好比发生火灾时,一旦着了大火,想要不造成损失去 ...

  4. 用信号量实现进程互斥示例和解决哲学家就餐问题

    用信号量实现进程互斥示例和解决哲学家就餐问题 参考文章: (1)用信号量实现进程互斥示例和解决哲学家就餐问题 (2)https://www.cnblogs.com/alantu2018/p/84731 ...

  5. python解决哲学家就餐问题(and型信号量)

    最近操作系统刚学完这部分内容,老师要求下去自己实践一下,在网上看了看发现用python解决该问题的博文很少,而且好多都是错的,于是就自己写了一段代码 # and型信号量解决哲学家就餐问题 import ...

  6. 哲学家就餐问题c语言_哲学家就餐问题的一种Python解决方案

    哲学家就餐问题一直是多线程同步问题的经典案例,本文中展示了多线程竞争共享资源带来的死锁问题,并介绍了一种简单的解决方案. 哲学家就餐问题 哲学家最擅长的就是思考和吃饭 ,当他们感觉累的时候,就会拿起一 ...

  7. Python | 多线程死锁问题的巧妙解决方法

    死锁 死锁的原理非常简单,用一句话就可以描述完.就是当多线程访问多个锁的时候,不同的锁被不同的线程持有,它们都在等待其他线程释放出锁来,于是便陷入了永久等待.比如A线程持有1号锁,等待2号锁,B线程持 ...

  8. 【操作系统/OS笔记14】经典同步问题:读者-写者问题、哲学家就餐问题

    本次笔记内容: 10.6 经典同步问题-1 10.7 经典同步问题-2 10.8 经典同步问题-3 10.9 经典同步问题-4 10.10 经典同步问题-5 10.11 经典同步问题-6 文章目录 读 ...

  9. Thinking in Java---从哲学家就餐问题看死锁现象

    我们知道一个对象可以有synchronized方法或其他形式的加锁机制来防止别的线程在互斥还没释放的时候就访问这个对象.而且我们知道线程是会变成阻塞状态的(挂起),所以有时候就会发生死锁的情况:某个任 ...

  10. 哲学家就餐问题实验报告

    哲学家就餐问题 两个地方应该是pv操作,pv都是操作元语,不可中断 p操作是将信号量-1 v操作是将信号量+1 pv一定要配对使用 哲学家进餐可以通过信号量机制解决,避免死锁 注释如下: test(i ...

最新文章

  1. 关于博客园与CSDN博客同步的说明
  2. cisco 2811 安装HWIC-2FE卡 升级IOS 记录
  3. 【云计算】_8云视频与通信服务(完结)
  4. 移动端双指缩放、旋转
  5. stl-map的一道很好的题目
  6. python源码精要(9)-CPython内部原理快速指南(1)
  7. 也来谈谈RNN的梯度消失/爆炸问题
  8. 飞鸽传书2012是否发布了?
  9. mysql 表示时间_MySQL-时间(time、date、datetime、timestamp和year)
  10. 将EnyimMemcached从.NET Core RC1升级至RC2
  11. viewgroup的使用方法
  12. 复化科特斯公式matlab_基于牛顿—科特斯积分的误差分析
  13. 百度C语言面试题2017,百度C语言面试题
  14. jqGrid设置三级表头和表头合并
  15. Sonatype Nexus Maven仓库搭建和管理
  16. 《设计进化论日本版式设计速查手查手册》菜单版式
  17. 千百万Java开发者的福音:跨平台Cocos2d-Java游戏引擎诞生
  18. php函数 去空格,php删除空格函数是什么
  19. 如何获得指定进程的主窗口
  20. [MRCTF2020]PYWebsite -wp

热门文章

  1. 如何把caj转成pdf
  2. 元宇宙,风口还是扯淡?
  3. 四川取消英语计算机考试,2020年起,四川将不再承接全国英语等级考试,已有多省份停考!...
  4. 播布客里小布老师的全部视频收集
  5. 播布客里小布老师的所有视频收集
  6. mysql_front安装_MySql5.5安装步骤及MySql_Front视图配置
  7. 网络安全技术(第4版)复习资料整理
  8. linux下texlive的卸载,linux下安装TexLive
  9. 大学四年Java学习路线规划,所有私藏资料我都贡献出来了,我要是早知道就好了
  10. uniapp显示彩色的阿里icon的图标