随笔之---java版本哲学家就餐问题【信号量的实现】

2023-11-18

很喜欢这样的描述如果你喜欢也不防读一读

   从许多许多年前,石头就呆在那座岭上了。那是座无名的低岭,毫不起眼,没有足以称道的风景名胜,那块石头只是许多石头中的一颗,见证过日升日落,经历过沧海桑田,承受四季变迁。黄河水数度从它的身上淹没而过,人群在周围来来去去时,放牛的孩子偶尔也在它的身上歇脚。在许久许久的光阴里,它都没有挪动过位置了

可以收获:
对线程间的同步互斥有更深的理解。

对问题的解决如何抽象到具体转为一个具像的代码流程梳理。这很重要!!
例子可以直接跑起来,直接copy!
问题描述:
哲学家就餐问题是1965年由Dijkstra提出的一种线程同步的问题。

问题描述:一圆桌前坐着5位哲学家,两个人中间有一只筷子,桌子中央有面条。哲学家思考问题,当饿了的时候拿起左右两只筷子吃饭,必须拿到两只筷子才能吃饭。上述问题会产生死锁的情况,当5个哲学家都拿起自己右手边的筷子,准备拿左手边的筷子时产生死锁现象。
如图:
在这里插入图片描述

解决办法:
要么拿起两个筷子,要么不拿起。

思路梳理

指导原则:要么不拿,要么就拿两把叉子

  • 思考中
  • 进入饥饿状态
  • 左右没有邻居进餐进行下一步,否则等待
  • 吃面条
  • 放下左侧叉子,【如果邻居饿了可以提醒左邻居】
  • 放下右侧叉子,【如果邻居饿了可以提醒右邻居】
  • 新的循环又开始了,从第一步开始吧!

繁琐啰嗦的步骤抽象成符合人类阅读习惯的可读的api

while(true){
	think();
	takeForks();
	eating();
	putForks();
}



先思考,五个哲学家,相当于五个线程,他们要相互通信才能协调,礼让才能让大家都吃到面条。
那么必然有个哲学家的 数据结构,至少包含以下数据结构

通过思路梳理

  1. 状态类型: 每个哲学家要有三种状态: 思考,饥饿,吃饭饥饿为发起吃面条的请求,就是要拿叉子了。
  2. 状态数组: 放下叉子的时候要提醒邻居饥饿的邻居吃饭,即要有其他哲学家的状态,所以要有一个状态数组保存个个哲学家状态。这是一个共享的。
  3. 哲学家编号:由 2 可知数组的索引坐标,要和哲学家做关联映射,表明哪个哲学家对应自己的状态,所以数据结构里有当前哲学家的编号。
  4. 信号量:而每个哲学家不同状态是可以在三种类型中变化的,所以共享的状态数组要被读写,要加一个锁操作,保证互斥操作。
  5. 左右节点的引用:要通知左右邻居,则必然持有左右邻居的引用。
  6. 信号量数组: 要通知邻居,则设计到线程唤醒操作,这里使用信号量,所以每个哲学家要持有一个所有哲学家对应自己的信号量,方便其他人通知自己。
package com.mytest.osStudy;

import lombok.Data;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @ClassName: PhilosopherQuestion
 * @Description: os 解决 哲学家就餐问题
 * 核心思路:两个叉子要么都拿起来,要么一个不拿
 * 核心实现:通过每个哲学家自身的状态,吃饭代表两个叉子都有,用信号量实现 获得许可证代表有左右叉子!!
 * @Author fjp
 * @Date 2021/7/2-16:56
 * @Version 1.0
 */
public class PhilosopherQuestion {



    @Data
    static
    class Philosopher {
        // 当前哲学家的编号
        private int current;
        private Philosopher left;
        private Philosopher right;
        private int[] state;
        private int count;
        //这一定是共享的全局,方便唤醒其他人
        private Semaphore[] semaphores;
        // 为保证对  state[]  的安全读写
        private Semaphore mutex;

        public Philosopher(int current, int[] states, Semaphore[] semaphores, Semaphore mutext) {
            this.current = current;
            this.state = states;
            this.semaphores = semaphores;
            this.mutex = mutext;

        }


        public void think() throws InterruptedException {
            mutex.acquire();
            state[current] = 0;
            mutex.release();

        }


        //拿叉子要改变状态,要判断邻居是否没有吃饭才能拿叉子
        public void takeForks() throws InterruptedException {
            mutex.acquire();
            state[current] = 1;
            testTakeForks(this);          // 能获得叉子的人,会得到一个许可证!!!
            mutex.release();

            semaphores[current].acquire();   //没有获得叉子的人,就是没有得到许可证的人,会被阻塞!!
        }

        // index  哲学家的编号
        private void testTakeForks(Philosopher p) throws InterruptedException {
            //检查左右邻居的状态
            String time = new SimpleDateFormat("yyyy-MM-hh HH:mm:sss").format(new Date());
            if (state[p.current] == 1 && state[p.left.current] != 2 && state[p.right.current] != 2) {
                //状态赋值为2  进入吃饭状态,表示拿到了两个叉子
                state[p.current] = 2;
                //真正拿到叉子,需要用属于当前哲学家的信号量来约束的,但信号量使用的前提又要靠左右哲学家状态来前提来判断运用,而哲学家的状态又要用mutex 来保证安全
                semaphores[p.current].release();
                System.out.println(time + "\t我是哲学家p" + current + "\t 第\t" + (++count) + "\t次拿到叉子" + "\t当前所有人状态:\t" + Arrays.toString(state));
            } else {

                // 两边的邻居有吃饭的,或者自己没有进入 进入饥饿状态的的,一定要记住,饥饿状态才是发起了抢叉子的请求!!!
                System.out.println(time + "\t我是哲学家p" + current + "\t 不具备拿叉子条件【我可能是被邻居唤醒检测】\t当前count:" + (count) + "\t当前所有人状态:\t" + Arrays.toString(state));
            }
        }

        private void eating() throws InterruptedException {

            //todo eat
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public void putForks() throws InterruptedException {

            mutex.acquire();

            state[current] = 0;//表明放下了叉子


            //此时要检查左右邻居
            testTakeForks(left);
            testTakeForks(right);
            mutex.release();

        }


        public void philospherDay() {
            while (true) {
                try {
                    think();
                    takeForks();
                    eating();
                    putForks();
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }

        }


    }
    // 测试代码
    public static void main(String[] args) throws InterruptedException {
        // int THINKING = 0;  /* 思考*/     int HUNGERY = 1;   /*饥饿*/      int EATING = 2;    // 吃饭
        int count = 5;// 5个哲学家
        int[] state = new int[5];
        Semaphore[] semaphores = new Semaphore[5];  //索引关联  例如0编号哲学家 则对应semaphores[0]
        Semaphore mutex = new Semaphore(1);//
        //所以哲学家 最开始都是思考状态
        for (int i = 0; i < 5; i++) {
            state[i] = 0;
        }
        // 索引下标对应 哲学家编号,归属哪个哲学家。
        for (int i = 0; i < 5; i++) {
            semaphores[i] = new Semaphore(0);

        }


        Philosopher p0 = new Philosopher(0, state, semaphores, mutex);
        Philosopher p1 = new Philosopher(1, state, semaphores, mutex);
        Philosopher p2 = new Philosopher(2, state, semaphores, mutex);
        Philosopher p3 = new Philosopher(3, state, semaphores, mutex);
        Philosopher p4 = new Philosopher(4, state, semaphores, mutex);


        p0.setLeft(p4);
        p0.setRight(p1);
        p1.setLeft(p0);
        p1.setRight(p2);
        p2.setLeft(p1);
        p2.setRight(p3);
        p3.setLeft(p2);
        p3.setRight(p4);
        p4.setLeft(p3);
        p4.setRight(p0);


        ExecutorService pool = Executors.newFixedThreadPool(5);
        pool.submit(() -> p0.philospherDay());
        pool.submit(() -> p1.philospherDay());
        pool.submit(() -> p2.philospherDay());
        pool.submit(() -> p3.philospherDay());
        pool.submit(() -> p4.philospherDay());


        Thread.sleep(100000);

    }


}



运行结果如下:

2021-07-11 23:58:034	我是哲学家p0	 第	1	次拿到叉子	当前所有人状态:	[2, 0, 0, 0, 0]
2021-07-11 23:58:034	我是哲学家p1	 不具备拿叉子条件【我可能是被邻居唤醒检测】	当前count:0	当前所有人状态:	[2, 1, 0, 0, 0]
2021-07-11 23:58:034	我是哲学家p2	 第	1	次拿到叉子	当前所有人状态:	[2, 1, 2, 0, 0]
2021-07-11 23:58:034	我是哲学家p3	 不具备拿叉子条件【我可能是被邻居唤醒检测】	当前count:0	当前所有人状态:	[2, 1, 2, 1, 0]
2021-07-11 23:58:034	我是哲学家p4	 不具备拿叉子条件【我可能是被邻居唤醒检测】	当前count:0	当前所有人状态:	[2, 1, 2, 1, 1]
2021-07-11 23:58:034	我是哲学家p2	 不具备拿叉子条件【我可能是被邻居唤醒检测】	当前count:1	当前所有人状态:	[2, 1, 0, 1, 1]
2021-07-11 23:58:034	我是哲学家p2	 第	2	次拿到叉子	当前所有人状态:	[2, 1, 0, 2, 1]
2021-07-11 23:58:034	我是哲学家p2	 不具备拿叉子条件【我可能是被邻居唤醒检测】	当前count:2	当前所有人状态:	[2, 1, 1, 2, 1]
2021-07-11 23:58:034	我是哲学家p0	 不具备拿叉子条件【我可能是被邻居唤醒检测】	当前count:1	当前所有人状态:	[0, 1, 1, 2, 1]
2021-07-11 23:58:034	我是哲学家p0	 第	2	次拿到叉子	当前所有人状态:	[0, 2, 1, 2, 1]
2021-07-11 23:58:034	我是哲学家p0	 不具备拿叉子条件【我可能是被邻居唤醒检测】	当前count:2	当前所有人状态:	[1, 2, 1, 2, 1]
2021-07-11 23:58:034	我是哲学家p3	 不具备拿叉子条件【我可能是被邻居唤醒检测】	当前count:0	当前所有人状态:	[1, 2, 1, 0, 1]
2021-07-11 23:58:034	我是哲学家p3	 第	1	次拿到叉子	当前所有人状态:	[1, 2, 1, 0, 2]
2021-07-11 23:58:034	我是哲学家p3	 不具备拿叉子条件【我可能是被邻居唤醒检测】	当前count:1	当前所有人状态:	[1, 2, 1, 1, 2]
2021-07-11 23:58:034	我是哲学家p1	 不具备拿叉子条件【我可能是被邻居唤醒检测】	当前count:0	当前所有人状态:	[1, 0, 1, 1, 2]
2021-07-11 23:58:034	我是哲学家p1	 第	1	次拿到叉子	当前所有人状态:	[1, 0, 2, 1, 2]

符合预期!!!,不相邻的哲学家可以同时吃饭

赠人玫瑰,手有余香,你的关注,我的动力 ,谢谢!
更多分享可见:​​​​​​​

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

随笔之---java版本哲学家就餐问题【信号量的实现】 的相关文章

随机推荐

  • SVN导出版本增量包

    showlog 选择一个或者多个版本 右键 Compare with previous version 选择一个或者多个文件 右键 Expore selection to 最后导出的文件会有其相应的路径
  • 全志F1C200s芯片处理器参数介绍

    F1C200s是全志的一款高度集成 低功耗的移动应用处理器 可用于多种多媒体音视频设备中 全志F1C200s基于ARM 9架构 集成了DDR 它支持高清视频解码 包括H 264 H 263 MPEG 1 2 4等 它还集成了音频编解码器和I
  • cucumber测试_延长Cucumber测试生命周期

    cucumber测试 总览 本文涉及两件事 我如何使beforeAll和AfterAll生命周期事件在Cucumber中发生 在Cucumber测试运行之前 如何使用TestContainers设置被测系统 不 您正在尝试在博客上进行SEO
  • node.js升级报错digital envelope routines unsupporte最简单解决方案

    背景 本地将nodejs 16升级成nodejs18运行时报错digital envelope routines unsupported 报错 Error error 0308010C digital envelope routines u
  • cytoscape插件下载_cytoscape五步曲之三:安装各种插件

    软件安装我就不多说了 直接去官网下载即可 请务必下载3 x版本 我讲的是 最新版教程 本次讲解如何给cytoscape安装插件 cytoscape本身是一个平台 学者可以在上面开发各种各样功能的插件实现不同的分析需求 类似于R语言这个平台
  • mysql中varbinary什么意思_MySQL中的数据类型binary和varbinary详解

    前言 BINARY和VARBINARY与 CHAR和VARCHAR类型有点类似 不同的是BINARY和VARBINARY存储的是二进制的字符串 而非字符型字符串 也就是说 BINARY和VARBINARY没有字符集的概念 对其排序和比较都是
  • 当我被酱香拿铁刷屏后......

    这两天 朋友圈刮起了酱香风 跨界里的新宠儿酱香拿铁卖爆了 不得不说瑞幸是懂跨界的 短短一天时间 酱香拿铁已售出 542 万杯 销售额超一亿元 谁能想到年轻人的第一杯茅台竟然是瑞幸卖出去的 这可能也是星巴克最无语的一天吧 瑞幸的订单长到可以直
  • python多进程cpu的占用率很低_Python 中的进程池与多进程

    封面图片来源 沙沙野 内容概览 进程池 进程池和多进程的性能测试 进程池的其他机制 进程池的回调函数 进程池 如果有多少个任务 就开启多少个进程 实际上并不划算 由于计算机的 cpu 个数是非常有限的因此开启的进程数量完全和 cpu 个数成
  • LOAM算法详解

    激光SLAM 帧间匹配方法 Point to Plane ICP NDT Feature based Method 回环检测方法 Scan to Scan Scan to Map LOAM创新点 定位和建图的分离 里程计模块 高频低质量的帧
  • 在pycharm中更新pip失败

    尝试了网上的各种方法 各种翻车 删除虚拟环境中的这两个文件夹 包括pip 有只删除pip 21 1 2 dist info这个个文件夹然后重新安装pip之后在更新 我试了没有用 下载 get pip py 文件 转到 https boots
  • drive数据集_英伟达的最强人脸GAN开源了,它吃的高清数据集也开源了

    栗子 假装发自 凹非寺 量子位 出品 公众号 QbitAI 你大概还没忘记 英伟达去年年底推出的GAN 它合成的人脸甚至骗得过肉眼 如今 它终于有了自己的名字 叫StyleGAN 顾名思义 GAN的生成器 是借用风格迁移的思路重新发明的 能
  • Docker 入门笔记

    狂神说Java Docker最新超详细版教程通俗易懂 视频地址 https www bilibili com video BV1og4y1q7M4 share source copy web Docker安装 基本组成 说明 镜像 imag
  • 小米2020校招软件开发工程师笔试题二

    1 计算大于n n gt 1 的最小的斐波那契数 以下划线出应填入 B function f n int int a new int 2 a 0 a 1 1 int i 1 while true i i 1 2 a i If a i gt
  • C++标准库--正态分布类 std::normal_distribution

    参考链接 https en cppreference com w cpp numeric random normal distribution std normal distribution是C 11提供的一个正态分布函数模板类 头文件 i
  • 在matlab中使用遗传算法执行最优化

    遗传算法是一种通用的最优化方法 具体原理可以看 遗传算法详解与实验 下面记录在Matlab中如何使用遗传算法来做优化 用法 调用方式如下 1 x ga fun nvars 2 x ga fun nvars A b 3 x ga fun nv
  • webpack之sideEffects

    webpack之sideEffects 前言 一 sideEffects的使用 二 sideEffects注意事项 前言 webpack4新增了一个sideEffects新特性 它允许我们通过配置的方式 去标识我们的代码是否有副作用 从而为
  • 云计算的概念、原理和关键技术

    1 云计算的定义 NIST 美国国家标准及技术研究所 对云计算的定义 云计算是一种模型 实现无处不在的 方便 通过网络按需访问的可配置的共享计算资源池 例如 网络 服务器 存储 应用程序 服务 这些资源可以快速提供 通过最小化管理成本或与服
  • jsp下读取c:forEach的循环次数,以及内部循环数据累加统计等

    前言 近日接触到一个比较旧的项目 框架使用的是Status2 Spring3 前端jsp大量内嵌了java代码 几乎未使用jstl和el表达式 个人习惯原因 已经很不喜欢使用这种通过写java代码在jsp上做逻辑控制的方式 很不好让别人读代
  • input checkbox js控制单选

    html中checkbox的格式如下 div div div div
  • 随笔之---java版本哲学家就餐问题【信号量的实现】

    很喜欢这样的描述如果你喜欢也不防读一读 从许多许多年前 石头就呆在那座岭上了 那是座无名的低岭 毫不起眼 没有足以称道的风景名胜 那块石头只是许多石头中的一颗 见证过日升日落 经历过沧海桑田 承受四季变迁 黄河水数度从它的身上淹没而过 人群