文章目录
哲学家就餐问题
哲学家就餐问题 如下图所示. 哲学家就餐的餐位上, 左手边和右手边分别只有一只筷子.
如果要就餐, 就必须拿到左手边和右手边的筷子.
流程如下:
先拿起左手的筷子
再拿起右手的筷子
如果筷子被人使用了, 那就等别人用完
吃完后, 把筷子放回原位
哲学家就餐流程的伪代码如下:
就餐问题的死锁风险 :
如果某个时刻, 每个哲学家都拿着左手边的筷子, 那么就会永远等待右手边的筷子,
哲学家就餐问题代码演示
如下为哲学家就餐问题的代码演示. 定义了Philosopher 的哲学家内部类. 实现了Runnable接口, 重写里面的run方法, 执行上面提到的哲学家每天做的事情.
- 思考
- 拿起左边筷子 (拿到左边筷子的锁)
- 拿起右边筷子(拿到右边筷子的锁) --> 吃饭
- 放下右边筷子 (释放右边筷子的锁)
- 放下左边筷子 (释放左边筷子的锁 )
并且定义了哲学家对象的构造方法, 传入左边筷子与右边筷子 对象.
在main 方法中, 初始化了筷子. 初始化了哲学家. 新建线程中传入哲学家对象 ,并且启动线程.
package com.thread.deadlock;
/**
* 类名称:DiningPhilosophers
* 类描述: 演示哲学家就餐问题导致的死锁
*
* @author: https://javaweixin6.blog.csdn.net/
* 创建时间:2020/9/12 15:50
* Version 1.0
*/
public class DiningPhilosophers {
public static void main(String[] args) {
//建立五个哲学家的数组
Philosopher[] philosophers = new Philosopher[5];
//定义筷子的个数, 为哲学家的数量
Object[] chopsticks = new Object[philosophers.length];
//初始化筷子
for (int i = 0; i < chopsticks.length; i++) {
chopsticks[i] = new Object();
}
//初始化哲学家
for (int i = 0; i < philosophers.length; i++) {
//获取左边筷子 与右边筷子
Object leftChopstick = chopsticks[i];
//右边的筷子为对数组长度的取余运算 避免数组越界 5%5=0 3%5=3
Object rightChopstick = chopsticks[(i+1)%chopsticks.length ];
//初始化哲学家
philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
//创建并启动线程, 给线程命名
new Thread(philosophers[i],"哲学家"+ (i+1) +"号" ).start();
}
}
/**
* 哲学家的内部类
*/
public static class Philosopher implements Runnable {
private Object leftChopstick;
private Object rightChopstick;
//初始化的时候, 传入两个对象 左边筷子 与右边筷子
public Philosopher(Object leftChopstick, Object rightChopstick) {
this.leftChopstick = leftChopstick;
this.rightChopstick = rightChopstick;
}
@Override
public void run() {
try {
//哲学家执行的任务
while (true) {
//思考
doPrintAndWait("Thinking");
//拿起左边筷子
synchronized (leftChopstick) {
doPrintAndWait("Picked up left chopstick");
//拿起右边筷子 //吃饭
synchronized (rightChopstick) {
doPrintAndWait("Picked up right chopstick - eating");
//放下右边筷子, 释放锁
doPrintAndWait("Put down right chopstick");
}
//放下左边的筷子, 释放锁
doPrintAndWait("Put down left chopstick");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//打印和执行等待的方法
private void doPrintAndWait(String action) throws InterruptedException {
//打印出线程的名称 和 行为
System.out.println(Thread.currentThread().getName() + " " + action);
//休眠随机的时间
Thread.sleep((long) (Math.random() * 10));
}
}
}
运行程序, 可以看到某一次的打印结果如下 .
五个哲学家思考, 思考完了之后, 五个哲学家都拿着左边的筷子, 导致程序死锁, 无法拿到右边的筷子 ,无法继续执行下去 .
再次多次运行程序, 可以看到如下的情况. 此时虽然程序打印了一段时间, 看似没有死锁, 但是过了一会之后, 还是会发生了死锁.
如下图所示, 发生死锁的时候, 一定是所有的哲学家此时都是拿着左边的筷子的.