zoukankan      html  css  js  c++  java
  • 哲学家就餐问题

    问题描述
    哲学家就餐问题也被称为刀叉问题,或者吃面问题。我们先来描述一下这个问题所要说明的事情,这个问题如下图所示:

    image-20210129140231627

    有 5 个哲学家,他们面前都有一双筷子,即左手有一根筷子,右手有一根筷子。当然,这个问题有多个版本的描述,可以说是筷子,也可以说是一刀一叉,因为吃牛排的时候,需要刀和叉,缺一不可,也有说是用两把叉子来吃意大利面。这里具体是刀叉还是筷子并不重要,重要的是必须要同时持有左右两边的两个才行,也就是说,哲学家左手要拿到一根筷子,右手也要拿到一根筷子,在这种情况下哲学家才能吃饭。为了方便理解,我们选取和我国传统最贴近的筷子来说明这个问题。

    为什么选择哲学家呢?因为哲学家的特点是喜欢思考,所以我们可以把哲学家一天的行为抽象为思考,然后吃饭,并且他们吃饭的时候要用一双筷子,而不能只用一根筷子。

    1. 主流程

    我们来看一下哲学家就餐的主流程。哲学家如果想吃饭,他会先尝试拿起左手的筷子,然后再尝试拿起右手的筷子,如果某一根筷子被别人使用了,他就得等待他人用完,用完之后他人自然会把筷子放回原位,接着他把筷子拿起来就可以吃了(不考虑卫生问题)。这就是哲学家就餐的最主要流程。

    1. 流程的伪代码

    根据我们的逻辑规定,在拿起左手边的筷子之后,下一步是去拿右手的筷子。大部分情况下,右边的哲学家正在思考,所以当前哲学家的右手边的筷子是空闲的,或者如果右边的哲学家正在吃饭,那么当前的哲学家就等右边的哲学家吃完饭并释放筷子,于是当前哲学家就能拿到了他右手边的筷子了。

    但是,如果每个哲学家都同时拿起左手的筷子,那么就形成了环形依赖,在这种特殊的情况下,每个人都拿着左手的筷子,都缺少右手的筷子,那么就没有人可以开始吃饭了,自然也就没有人会放下手中的筷子。这就陷入了死锁,形成了一个相互等待的情况。代码如下所示:

    /**
     * @author WGR
     * @create 2021/1/29 -- 14:08
     */
    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(0.5);
        }
    }
    
    class Chopstick {
        String name;
    
        public Chopstick(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "筷子{" + name + '}';
        }
    }
    
    

    结果:

    image-20210129141257327

    活锁

    活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如

    /**
     * @author WGR
     * @create 2021/1/29 -- 14:22
     */
    @Slf4j(topic = "c.TestLiveLock")
    public class TestLiveLock {
        static volatile int count = 10;
        static final Object lock = new Object();
    
        public static void main(String[] args) {
            new Thread(() -> {
                // 期望减到 0 退出循环
                while (count > 0) {
                    sleep(0.2);
                    count--;
                    log.debug("count: {}", count);
                }
            }, "t1").start();
            new Thread(() -> {
                // 期望超过 20 退出循环
                while (count < 20) {
                    sleep(0.2);
                    count++;
                    log.debug("count: {}", count);
                }
            }, "t2").start();
        }
    }
    
    

    饥饿
    很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题
    ,先来看看使用顺序加锁的方式解决之前的死锁问题

    image-20210129142534742

    顺序加锁的解决方案

    image-20210129142558837

    改变一个哲学家拿筷子的顺序

    我们还可以利用死锁避免策略,那就是从逻辑上去避免死锁的发生,比如改变其中一个哲学家拿筷子的顺序。我们可以让 4 个哲学家都先拿左边的筷子再拿右边的筷子,但是有一名哲学家与他们相反,他是先拿右边的再拿左边的,这样一来就不会出现循环等待同一边筷子的情况,也就不会发生死锁了。

    image-20210129141542847

    解决:

    /**
     * @author WGR
     * @create 2021/1/29 -- 14:08
     */
    public class TestDeadLock2 {
        public static void main(String[] args) {
            Chopstick2 c1 = new Chopstick2("1");
            Chopstick2 c2 = new Chopstick2("2");
            Chopstick2 c3 = new Chopstick2("3");
            Chopstick2 c4 = new Chopstick2("4");
            Chopstick2 c5 = new Chopstick2("5");
            new Philosopher2("苏格拉底", c1, c2).start();
            new Philosopher2("柏拉图", c2, c3).start();
            new Philosopher2("亚里士多德", c3, c4).start();
            new Philosopher2("赫拉克利特", c4, c5).start();
            new Philosopher2("阿基米德", c5, c1).start();
        }
    }
    
    @Slf4j(topic = "c.Philosopher")
    class Philosopher2 extends Thread {
        Chopstick2 left;
        Chopstick2 right;
    
        public Philosopher2(String name, Chopstick2 left, Chopstick2 right) {
            super(name);
            this.left = left;
            this.right = right;
        }
    
        @Override
        public void run() {
            while (true) {
    
                // 尝试获得左手筷子
                if(left.tryLock()){
                    try {
                        // 尝试获得左手筷子
                        if (right.tryLock()) {
                            try {
                                eat();
                            }finally {
                                right.unlock();
                            }
                        }
                    }finally {
                        left.unlock();
                    }
    
                }
    
            }
        }
    
        Random random = new Random();
        private void eat() {
            log.debug("eating...");
            Sleeper.sleep(0.5);
        }
    }
    
    class Chopstick2 extends ReentrantLock {
        String name;
    
        public Chopstick2(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "筷子{" + name + '}';
        }
    }
    

    image-20210129145515227

  • 相关阅读:
    第2层交换和生成树协议(STP)__散知识点
    OSPF
    EIGRP和OSPF__EIGRP
    EIGRP和OSPF__邻居发现
    IP路由__距离矢量路由选择协议
    IP路由__动态路由
    IP路由__静态路由
    IP路由__IP路由选择过程
    Cisco的互联网络操作系统IOS和安全设备管理器SDM__CDP
    Cisco的互联网络操作系统IOS和安全设备管理器SDM__备份和恢复Cisco 配置
  • 原文地址:https://www.cnblogs.com/dalianpai/p/14345043.html
Copyright © 2011-2022 走看看