• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
正在努力成为一个优秀的废物
博客园    首页    新随笔    联系   管理    订阅  订阅
哲学家就餐问题的五个解决方法

问题描述:

5 个沉默寡言的哲学家围坐在圆桌前,每人面前一盘意面。叉子放在哲学家之间的桌面上。(5 个哲学家,5 根叉子)

所有的哲学家都只会在思考和进餐两种行为间交替。哲学家只有同时拿到左边和右边的叉子才能吃到面,而同一根叉子在同一时间只能被一个哲学家使用。每个哲学家吃完面后都需要把叉子放回桌面以供其他哲学家吃面。只要条件允许,哲学家可以拿起左边或者右边的叉子,但在没有同时拿到左右叉子时不能进食。

假设面的数量没有限制,哲学家也能随便吃,不需要考虑吃不吃得下。

设计一个进餐规则(并行算法)使得每个哲学家都不会挨饿;也就是说,在没有人知道别人什么时候想吃东西或思考的情况下,每个哲学家都可以在吃饭和思考之间一直交替下去。

 leetcode链接:https://leetcode.cn/problems/the-dining-philosophers/

这个问题是非常经典的操作系统死锁问题。如果五个哲学家同时拿起左边的叉子,并且等待右边叉子时不放下左手拿起的叉子,就会导致五个哲学家全都吃不到东西而饿死。

要解决这个问题,要先知道死锁发生的先决条件,才能设计算法避免死锁发生的情况。

死锁产生的条件:

互斥条件:该资源任何一个时刻只由一个线程占有。

请求与保持:一个线程因请求资源而阻塞时,不释放自己已经获取的资源。

不剥夺:线程已经获得的资源在未使用完成时不能被其他线程强行剥夺。

循环等待:若干线程之间形成一种头尾相接的循环资源等待关系。

如何预防死锁:

破坏请求与保持:占用部分资源的线程申请其他资源的时候,如果申请不到,可以主动释放它占有的资源。

一次性申请所有需要的资源 。

破坏循环等待:按照某一顺序申请资源,释放资源则反序。

解决方案一:

破坏循环等待:所有的资源按照顺序申请,给五个叉子编号0-4,哲学家进餐时,必须要先申请编号小的叉子,再申请编号大的叉子。

public class DiningPhilosophers {
    private ReentrantLock[] forks = new ReentrantLock[5];

    public DiningPhilosophers() {
        for (int i = 0; i < 5; i++) {
            forks[i] = new ReentrantLock();
        }
    }

    // call the run() method of any runnable to execute its code
    public void wantsToEat(int philosopher,
                           Runnable pickLeftFork,
                           Runnable pickRightFork,
                           Runnable eat,
                           Runnable putLeftFork,
                           Runnable putRightFork) throws InterruptedException {
        /*
         * 打破循环等待 1. 给叉子编号, 每次先拿起编号小的叉子, 再拿起编号大的叉子
         */
        int lFork = philosopher;
        int rFork = (philosopher + 1) % 5;
        forks[Math.min(lFork, rFork)].lock();
        forks[Math.max(lFork, rFork)].lock();
        pickLeftFork.run();
        pickRightFork.run();
        eat.run();
        putLeftFork.run();
        putRightFork.run();
        forks[Math.max(lFork, rFork)].unlock();
        forks[Math.min(lFork, rFork)].unlock();
    }

}

解决方案二:

破坏循环等待:五个哲学家进餐会导致死锁,那控制四个哲学家一起进餐,这样总有一个哲学家可以拿到左右两边的叉子。

public class DiningPhilosophers {
    private ReentrantLock[] forks = new ReentrantLock[5];
    private Semaphore semaphore = new Semaphore(4);

    public DiningPhilosophers() {
        for (int i = 0; i < 5; i++) {
            forks[i] = new ReentrantLock();
        }
    }

    // call the run() method of any runnable to execute its code
    public void wantsToEat(int philosopher,
                           Runnable pickLeftFork,
                           Runnable pickRightFork,
                           Runnable eat,
                           Runnable putLeftFork,
                           Runnable putRightFork) throws InterruptedException {
        /*
         打破循环等待  2. 5个哲学家同时进餐会形成死锁, 控制四个哲学家一起进餐
         */
        semaphore.acquire();
        int lFork = philosopher;
        int rFork = (philosopher + 1) % 5;
        forks[lFork].lock();
        forks[rFork].lock();
        pickLeftFork.run();
        pickRightFork.run();
        eat.run();
        putLeftFork.run();
        putRightFork.run();
        forks[lFork].unlock();
        forks[rFork].unlock();
        semaphore.release();
    }

}

解决方案三:

打破循环等待:控制拿叉子的顺序,奇数先拿左边,偶数先拿右边
public class DiningPhilosophers {
    private ReentrantLock[] forks = new ReentrantLock[5];

    public DiningPhilosophers() {
        for (int i = 0; i < 5; i++) {
            forks[i] = new ReentrantLock();
        }
    }
    // call the run() method of any runnable to execute its code
    public void wantsToEat(int philosopher,
                           Runnable pickLeftFork,
                           Runnable pickRightFork,
                           Runnable eat,
                           Runnable putLeftFork,
                           Runnable putRightFork) throws InterruptedException {
        
        /*
        打破循环等待 3. 控制拿叉子的顺序, 奇数先拿左边的, 偶数先拿右边的
         */
        int lFork = philosopher;
        int rFork = (philosopher + 1) % 5;
        if (philosopher % 2 == 0) {
            lFork = rFork;
            rFork = philosopher;
        }
        forks[lFork].lock();
        forks[rFork].lock();
        pickLeftFork.run();
        pickRightFork.run();
        eat.run();
        putLeftFork.run();
        putRightFork.run();
        forks[lFork].unlock();
        forks[rFork].unlock();
    }

}

解决方案四:

一次性申请到所有资源:设置一个临界区,只有一个线程可以进入去同时获取左右两把叉子。如果在临界区没有拿到两把叉子,需要退出临界区等待再次进入。如果哲学家们吃的很频繁,就有可能导致某些哲学家一直申请不到叉子而饿死

public class DiningPhilosophers {
    private boolean[] forkState = new boolean[]{false, false, false, false, false};
    private ReentrantLock lock = new ReentrantLock();

    public DiningPhilosophers() {
    }
  
    // call the run() method of any runnable to execute its code
    public void wantsToEat(int philosopher,
                           Runnable pickLeftFork,
                           Runnable pickRightFork,
                           Runnable eat,
                           Runnable putLeftFork,
                           Runnable putRightFork) throws InterruptedException {
        /*
        一次性申请到所有资源  设置临界区, 只有一个线程可以进入去获取左右两只叉子
         */
        int lFork = philosopher;
        int rFork = (philosopher + 1) % 5;
        while (true) {
            lock.lock();
            if (forkState[lFork] == false && forkState[rFork] == false) {
                forkState[lFork] = forkState[rFork] = true;
                break;
            }
            lock.unlock();
            Thread.sleep(1);
        }
        lock.unlock(); // 如果拿完叉子,吃,释放叉子都在临界区,就到导致进餐变成了串行,因此拿到叉子就释放锁,可以保证有两个哲学家在同时吃
        pickLeftFork.run();
        pickRightFork.run();
        eat.run();
        putLeftFork.run();
        putRightFork.run();
        // 释放叉子,叉子被拿起后,不会再被其他线程操作,因此放下叉子不存在写冲突。但是可能导致其他线程读到的是脏数据,但是其他线程会在拿不到叉子的时候一直读,因此读到一次脏数据并不影响
        forkState[lFork] = forkState[rFork] = false;
    }

}

 

解决方案五:

主动释放占有的资源:当五个哲学家都拿起左边的叉子时,让偶数编号的哲学家放弃自己拿起的叉子,但是牺牲其他线程的做法,在资源使用很频繁的时候,有可能导致部分线程饿死。

public class DiningPhilosophers {
    private ReentrantLock[] forks = new ReentrantLock[5];
public DiningPhilosophers() { for (int i = 0; i < 5; i++) { forks[i] = new ReentrantLock(); } } // call the run() method of any runnable to execute its code public void wantsToEat(int philosopher, Runnable pickLeftFork, Runnable pickRightFork, Runnable eat, Runnable putLeftFork, Runnable putRightFork) throws InterruptedException { int lFork = philosopher; int rFork = (philosopher + 1) % 5; while (true) { forks[lFork].lock(); if (forks[rFork].tryLock()) { break; } // 申请右边叉子失败 if(philosopher % 2 == 1) { // 如果哲学家编号为奇数, 可以继续持有已经申请到的资源 forks[rFork].lock(); break; } else { // 否则, 需要释放已经占有的左边叉子, 等待下一次重新申请 forks[lFork].unlock(); } Thread.sleep(1); } pickLeftFork.run(); pickRightFork.run(); eat.run(); putLeftFork.run(); putRightFork.run(); forks[lFork].unlock(); forks[rFork].unlock(); } }

 

posted on 2022-06-11 15:44  你算哪根小毛线  阅读(6282)  评论(1)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3