死锁——哲学家就餐问题


【死锁】这一篇文章中,我们学习了死锁相关的理论知识,本篇文章来看看死锁案例——哲学家就餐问题,这篇文章主要来讨论就餐问题的死锁和多种死锁修复的方法。

哲学家就餐

哲学家就餐问题

问题描述

看上图,有五位哲学家,面前都有一个盘子,盘子左边和右边都有一根筷子,他们在吃面之前需要先拿起左边的筷子再拿起右边的筷子,有了一双筷子就可以吃面了。

流程

  • 先拿起左手的筷子
  • 然后拿起右手的筷子
  • 如果筷子被人使用了,那就等别人用完
  • 吃完后,把筷子放回原位

思考

上面流程有死锁和资源耗尽的风险

  • 死锁:每个哲学家都拿着左手的筷子,永远在等右边的筷子(或者相反)

代码演示

哲学家:每个哲学家重复做的事就是:思考,拿筷子吃面

public 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) {
                doAction("思考中...");
                synchronized (leftChopstick) {
                    doAction("拿起左边筷子...");
                    synchronized (rightChopstick) {
                        doAction("拿起右边筷子,然后开始吃面...");
                        // 吧唧吧唧...吃面
                        doAction("放下右边筷子...");
                    }
                    doAction("放下左边筷子...");
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void doAction(String action) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " " + action);
        Thread.sleep((long) (Math.random() * 10));
    }
}

启动主类:

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];
            Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];
            philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
            new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();
        }
    }
}

打印结果:可以看到,五个哲学家最后都拿起了左边筷子,都在等右边筷子,就发生了死锁。

...
哲学家3号 拿起左边筷子...<
哲学家2号 思考中...
哲学家1号 拿起右边筷子,然后开始吃面...
哲学家1号 放下右边筷子...
哲学家1号 放下左边筷子...
哲学家2号 拿起左边筷子...<
哲学家5号 拿起右边筷子,然后开始吃面...
哲学家1号 思考中...
哲学家5号 放下右边筷子...
哲学家5号 放下左边筷子...
哲学家1号 拿起左边筷子...<
哲学家5号 思考中...
哲学家4号 拿起右边筷子,然后开始吃面...
哲学家4号 放下右边筷子...
哲学家4号 放下左边筷子...
哲学家5号 拿起左边筷子...<
哲学家4号 思考中...
哲学家4号 拿起左边筷子...<

解决哲学家就餐问题的4种方案

  1. 服务员检查(避免策略):引入一个服务员协调,就是说当哲学家要拿起筷子的时候,先询问服务员能否拿起,服务员就会检查拿起筷子是否会有死锁的发生,不会的话就允许哲学家拿起筷子,相反如果可能会发生死锁就不让哲学家拿起筷子。
  2. 改变一个哲学家拿叉子的顺序(避免策略):因为要发生死锁,一定是所有哲学家都拿起了左边的筷子,发生了死锁环路,但是假如有个哲学家不按照顺时针拿的话,就是说先拿右边筷子,这样就永远不会发生都在等右边筷子的死锁环路了。
  3. 餐票(避免策略):就是说每个人吃饭前都要先拿到餐票才能拿筷子吃面,餐票总共只有4张,也就是说一定有个人能拿到一双筷子吃面,但是吃完餐票也要还回去。
  4. 领导调节(检测与恢复策略):并不是不让你发生死锁,而是等你死锁了,领导检测到了死锁发生(五个人都拿起了左边的筷子),就会命令其中一个人放下筷子,让别人先吃。

改变一个哲学家拿叉子的顺序(代码演示)

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];
            Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];
            //这里我们将最后一个哲学家拿筷子的顺序反过来
            if (i == philosophers.length - 1) {
                philosophers[i] = new Philosopher(rightChopstick, leftChopstick);
            } else {
                philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
            }
            new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();
        }
    }
}

打印结果:程序会一直运行下去,不再发生死锁。

哲学家1号 思考中...
哲学家3号 思考中...
哲学家4号 思考中...
哲学家2号 思考中...
哲学家5号 思考中...
哲学家4号 拿起左边筷子...
哲学家2号 拿起左边筷子...
哲学家4号 拿起右边筷子,然后开始吃面...
哲学家2号 拿起右边筷子,然后开始吃面...
哲学家5号 拿起左边筷子...
哲学家4号 放下右边筷子...
哲学家4号 放下左边筷子...
哲学家5号 拿起右边筷子,然后开始吃面...
哲学家4号 思考中...
哲学家5号 放下右边筷子...
哲学家2号 放下右边筷子...
...

笔记来源:慕课网悟空老师视频《Java并发核心知识体系精讲》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

✦昨夜星辰✦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值