Java并发编程:死锁场景之哲学家就餐问题

文章通过一个Java实现的示例展示了五个哲学家用餐时可能出现的死锁情况,每个哲学家持有两根筷子中的一根并试图获取另一根,导致所有哲学家都无法进食。为了解决这个问题,文章提出了使用Semaphore来防止死锁,通过限制同时获取筷子的数量,确保至少有一位哲学家可以进食并释放资源。
摘要由CSDN通过智能技术生成

这是一个常见的死锁场景

题目来源是力扣

自己去搜就行了,下面直接上代码

package cn.itcast.n4.deadlock.v1;

import cn.itcast.n2.util.Sleeper;
import lombok.extern.slf4j.Slf4j;

import java.util.Random;

public class TestDeadLock {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("苏格拉底", c1, c2).start();
        new Philosopher("柏拉图", c2, c3).start();
        new Philosopher("亚里士多德", c3, c4).start();
        new Philosopher("赫拉克利特", c4, c5).start();
        new Philosopher("阿基米德", c5, c1).start();
    }
}

@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            // 尝试获得左手筷子
            synchronized (left) {
                // 尝试获得右手筷子
                synchronized (right) {
                    eat();
                }
            }
        }
    }

    Random random = new Random();
    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(1); // 吃1秒钟
        log.debug("eating done...");
    }
}

class Chopstick {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

上述情况就容易发生5个哲学家都拿着一根筷子

同时也想拿另一根筷子

就陷入了死锁

-------------------------------------------------------------------------
名称: 阿基米德
状态: cn.itcast.Chopstick@1540e19d (筷子1) 上的BLOCKED, 拥有者: 苏格拉底
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
 - 已锁定 cn.itcast.Chopstick@6d6f6e28 (筷子5)
-------------------------------------------------------------------------
名称: 苏格拉底
状态: cn.itcast.Chopstick@677327b6 (筷子2) 上的BLOCKED, 拥有者: 柏拉图
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
 - 已锁定 cn.itcast.Chopstick@1540e19d (筷子1)
-------------------------------------------------------------------------
名称: 柏拉图
状态: cn.itcast.Chopstick@14ae5a5 (筷子3) 上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
 - 已锁定 cn.itcast.Chopstick@677327b6 (筷子2)
-------------------------------------------------------------------------
名称: 亚里士多德
状态: cn.itcast.Chopstick@7f31245a (筷子4) 上的BLOCKED, 拥有者: 赫拉克利特
总阻止数: 1, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
 - 已锁定 cn.itcast.Chopstick@14ae5a5 (筷子3)
-------------------------------------------------------------------------
名称: 赫拉克利特
状态: cn.itcast.Chopstick@6d6f6e28 (筷子5) 上的BLOCKED, 拥有者: 阿基米德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
 - 已锁定 cn.itcast.Chopstick@7f31245a (筷子4)

解决哲学家进食问题

class DinnerExecutor {
    static final Semaphore fork1 = new Semaphore(1);
    static final Semaphore fork2 = new Semaphore(1);
    static final Semaphore fork3 = new Semaphore(1);
    static final Semaphore fork4 = new Semaphore(1);
    static final Semaphore fork5 = new Semaphore(1);
    // 防止死锁的发生,给了4个控制权
    static final Semaphore forkControl = new Semaphore(4);

    public static void main(String[] args) {
        new Thread(new Philosopher(1, fork5, fork1)).start();
        new Thread(new Philosopher(2, fork1, fork2)).start();
        new Thread(new Philosopher(3, fork2, fork3)).start();
        new Thread(new Philosopher(4, fork3, fork4)).start();
        new Thread(new Philosopher(5, fork4, fork5)).start();
    }

    static class Philosopher implements Runnable {
        int no;             // 哲学家编号
        Semaphore left;     // 哲学家左边的叉子
        Semaphore right;    // 哲学家右边的叉子

        public Philosopher(int no, Semaphore left, Semaphore right) {
            this.no = no;
            this.left = left;
            this.right = right;
        }

        public void run() {
            try {
                // 这是饭卡许可证,为了防止死锁的发生,有一位哲学家得不到吃饭权
                forkControl.acquire();
                boolean leftFirst = new Random().nextInt(2) % 2 == 0; // true,false
                // 计算叉子坐标
                int dot = (leftFirst ? no - 1 : no); // no - 1左,no右
                if (dot == 0) dot = 5; // 按顺时针放叉子的位置的话,坐标5就是编号为1哲学家叉子的左边
                // 预分配叉子的使用权,剩下的全靠哲学家抢占时间片的速度来决定哪位哲学家拿到了叉子
                    System.out.println(no + "拿到了吃饭卡,并且准备去拿" + (leftFirst ? "左边" : "右边") + "叉子=" + dot);
                if (leftFirst) {
                    left.acquire(); // 拿走叉子的使用权
                } else {
                    right.acquire(); // 拿走叉子的使用权
                }
                    System.out.println(no + "拿到了" + (leftFirst ? "左边" : "右边") + "叉子=" + dot);
                // 线程sleep方法,下面抢占叉子的概率是等可能的
                Thread.sleep(1000);
                // 哲学家们再次枪占叉子
                if (leftFirst) {
                    right.acquire();
                } else {
                    left.acquire();
                }
                int dot2 = (leftFirst ? no : no - 1);
                if (dot2 == 0) dot2 = 5;
                    System.out.println(no + "也拿到了另一边叉子=" + dot2);
                    System.out.println(this.no + "号哲学家在吃饭");
                // 下一次抢占也是等可能的
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 程序运行到此处
                // 一位哲学家成功进食并且放下了叉子
                // 正常的话,这里一共会被执行5次
                right.release();
                left.release();
                System.out.println(no + "释放了所有叉子");
                forkControl.release();
                System.out.println(no + "释放了吃饭卡");
                System.out.println(this.no + "号哲学家在休息");
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值