返回顶部
首页 > 资讯 > 后端开发 > Python >Java线程同步问题--哲学家就餐
  • 855
分享到

Java线程同步问题--哲学家就餐

2024-04-02 19:04:59 855人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录1.场景2.解决方案方法一:限制吃饭的哲学家人数方法二:找到一个左撇子哲学家 1.场景 有五位沉默的哲学家围坐在一张圆桌旁,他们一生都在吃东西和思考。 有五只筷子供他们使用,哲学

1.场景

有五位沉默的哲学家围坐在一张圆桌旁,他们一生都在吃东西和思考。

有五只筷子供他们使用,哲学家需要双手拿到一双筷子之后才能吃饭;吃完后会将筷子放下继续思考。

那么现在有一个问题,我们需要想出一种方案,如何保证哲学家们可以交替吃饭和思考,而不会被饿死。

上面这个问题是由Dijkstra提出的一个经典的线程同步问题。

2.解决方案

我们在开始想如何解决问题之前,可以先将这个场景通过代码还原,在程序中进行建模。

每一只筷子可以看做是一个资源数据,都可以被它两边的哲学家尝试去获取,并且同一时间只能由其中一人持有,这可以通过我们JUC包中的信号量Semaphore来表示。

然后,每个哲学家可以看做是一个线程,每个线程中的run方法内容都是先进行思考,然后试图获取左右两边的筷子吃饭,吃完饭后继续思考。

通过上面的分析,我们的代码实现如下:


@Slf4j
public class DiningPhilosophers implements Runnable {

    private final int id;

    public DiningPhilosophers(int id) {
        this.id = id;
    }

    private static final Random random = new Random(System.currentTimeMillis());

    private static final Semaphore[] forks = new Semaphore[5];

    // 初始化信号量,每个信号量为1,代表1只筷子
    static {
        forks[0] = new Semaphore(1);
        forks[1] = new Semaphore(1);
        forks[2] = new Semaphore(1);
        forks[3] = new Semaphore(1);
        forks[4] = new Semaphore(1);
    }

    @Override
    public void run() {
        try {
            while (true) {
                think();
                eat(id);
            }
        } catch (InterruptedException e) {
            log.error("异常中断", e);
        }
    }

    
    private void think() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(random.nextInt(100));
    }

    private void eat(int id) {
        // TODO
    }

}

接下来,我们思考一下,如何实现哲学家吃饭的逻辑。

当一个哲学家需要吃饭时,他要拿起左右两边的筷子。

所以:

  • 哲学家 A(0) 需要筷子0 和 4
  • 哲学家 B(1) 需要筷子 1 和 0
  • 哲学家 C(2) 需要筷子 2 和 1
  • 哲学家 D(3) 需要筷子 3 和 2
  • 哲学家 E(4) 需要筷子 4 和 3

所以每个哲学家线程都应该有个编号,所以我在DiningPhilosophers中定义了属性id表示哲学家的编号。

在吃饭方法中,需要根据id来决定获取哪只筷子。

左手边的筷子可以有用fork[id]表示;

右手边的筷子用fork[(id+4)%5]表示。

那么我们的eat方法的实现如下:

private void eat(int id) throws InterruptedException {
    // 先拿左边的筷子
    forks[id].acquire();

    // 然后拿右边的筷子
    forks[(id + 4) % 5].acquire();

    // 吃一口饭
    log.info("哲学家{}正在吃饭~", id);

    // 依次放下左边的筷子和右边的筷子
    forks[id].release();
    forks[(id + 4) % 5].release();
}

我们接着来测试我们的完整代码。


@Slf4j
public class DiningPhilosophers implements Runnable {

    private final int id;

    public DiningPhilosophers(int id) {
        this.id = id;
    }

    private static final Random random = new Random(System.currentTimeMillis());

    private static final Semaphore[] forks = new Semaphore[5];

    // 初始化信号量,每个信号量为1,代表1只筷子
    static {
        forks[0] = new Semaphore(1);
        forks[1] = new Semaphore(1);
        forks[2] = new Semaphore(1);
        forks[3] = new Semaphore(1);
        forks[4] = new Semaphore(1);
    }

    @Override
    public void run() {
        try {
            while (true) {
                think();
                eat(id);
            }
        } catch (InterruptedException e) {
            log.error("异常中断", e);
        }
    }

    
    private void think() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(random.nextInt(100));
    }

    private void eat(int id) throws InterruptedException {
        // 先拿左边的筷子
        forks[id].acquire();

        // 然后拿右边的筷子
        forks[(id + 4) % 5].acquire();

        // 吃一口饭
        log.info("哲学家{}正在吃饭~", id);

        // 依次放下左边的筷子和右边的筷子
        forks[id].release();
        forks[(id + 4) % 5].release();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new DiningPhilosophers(i)).start();
        }
    }
}

运行上面的代码后,会发现程序在运行一段时间后会进入死状态。

这种情况是因为,在某一时刻,所有的哲学家都获取到了左手边的筷子,而无法获取到右手边的筷子,导致没有人可以到东西,陷入僵局。

该如何避免出现这种死锁问题呢?

方法一:限制吃饭的哲学家人数

很简单的一种方法,就是在一个时间点,只能有最多4个哲学家开始吃饭。4个哲学家分5只筷子,则永远不会发生死锁。

要实现这种方法,我们可以再定义一个许可数为4的信号量Semaphore,表示剩余可以吃饭的哲学家名额。

代码实现如下:


@Slf4j
public class DiningPhilosophers implements Runnable {

    private final int id;

    public DiningPhilosophers(int id) {
        this.id = id;
    }

    private static final Random random = new Random(System.currentTimeMillis());

    private static final Semaphore[] forks = new Semaphore[5];

    private static final Semaphore maxDiners = new Semaphore(4);


    // 初始化信号量,每个信号量为1,代表1只筷子
    static {
        forks[0] = new Semaphore(1);
        forks[1] = new Semaphore(1);
        forks[2] = new Semaphore(1);
        forks[3] = new Semaphore(1);
        forks[4] = new Semaphore(1);
    }

    @Override
    public void run() {
        try {
            while (true) {
                think();
                eat(id);
            }
        } catch (InterruptedException e) {
            log.error("异常中断", e);
        }
    }

    
    private void think() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(random.nextInt(100));
    }

    private void eat(int id) throws InterruptedException {
        // 先获得吃饭名额
        maxDiners.acquire();

        // 先拿左边的筷子
        forks[id].acquire();

        // 然后拿右边的筷子
        forks[(id + 4) % 5].acquire();

        // 吃一口饭
        log.info("哲学家{}正在吃饭~", id);

        // 依次放下左边的筷子和右边的筷子
        forks[id].release();
        forks[(id + 4) % 5].release();

        // 吃完之后归还吃饭名额
        maxDiners.release();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new DiningPhilosophers(i)).start();
        }
    }
}

方法二:找到一个左撇子哲学家

这种方法是让其中一个哲学家和其他哲学家拿筷子的顺序和其他哲学家不一样。

比如其他人都是先拿右手边再拿左手边,而这个左撇子哲学家则先拿左手边再拿右手边。

而哪一位哲学家被选为左撇子并不重要,因为桌子是圆的,所以我们就选择0号哲学家为左撇子。

代码实现如下:


@Slf4j
public class DiningPhilosophers implements Runnable {

    private final int id;

    public DiningPhilosophers(int id) {
        this.id = id;
    }

    private static final Random random = new Random(System.currentTimeMillis());

    private static final Semaphore[] forks = new Semaphore[5];


    // 初始化信号量,每个信号量为1,代表1只筷子
    static {
        forks[0] = new Semaphore(1);
        forks[1] = new Semaphore(1);
        forks[2] = new Semaphore(1);
        forks[3] = new Semaphore(1);
        forks[4] = new Semaphore(1);
    }

    @Override
    public void run() {
        try {
            while (true) {
                think();
                eat(id);
            }
        } catch (InterruptedException e) {
            log.error("异常中断", e);
        }
    }

    
    private void think() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(random.nextInt(100));
    }

    private void eat(int id) throws InterruptedException {
        if (id == 0) {
            hanleLeftFirst(id);
        } else {
            hanldRightFirst(id);
        }
        // 吃一口饭
        log.info("哲学家{}正在吃饭~", id);
        forks[id].release();
        forks[(id + 4) % 5].release();
    }

    private void hanleLeftFirst(int id) throws InterruptedException {
        forks[id].acquire();
        forks[(id + 4) % 5].acquire();
    }

    private void hanldRightFirst(int id) throws InterruptedException {
        forks[(id + 4) % 5].acquire();
        forks[id].acquire();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new DiningPhilosophers(i)).start();
        }
    }
}

到此这篇关于Java线程同步问题的文章就介绍到这了,更多相关Java线程同步内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java线程同步问题--哲学家就餐

本文链接: https://lsjlt.com/news/139116.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java线程同步问题--哲学家就餐
    目录1.场景2.解决方案方法一:限制吃饭的哲学家人数方法二:找到一个左撇子哲学家 1.场景 有五位沉默的哲学家围坐在一张圆桌旁,他们一生都在吃东西和思考。 有五只筷子供他们使用,哲学...
    99+
    2024-04-02
  • Java多线程之哲学家就餐问题详解
    目录一、题目二、题目解析三、代码实现四、运行效果截图五、结语一、题目 教材提供一个哲学家就餐问题的解决方案的框架。本问题要求通过pthreads 互斥锁来实现这个解决方案。 哲学家...
    99+
    2024-04-02
  • Java线程同步问题分析
    本篇内容主要讲解“Java线程同步问题分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java线程同步问题分析”吧!Java线程同步的基本实现思路还是比较容易理解的。我们可以给共享资源加一把锁...
    99+
    2023-06-17
  • Java线程同步问题实例分析
    这篇文章主要讲解了“Java线程同步问题实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java线程同步问题实例分析”吧!1.场景有五位沉默的哲学家围坐在一张圆桌旁,他们一生都在吃东西...
    99+
    2023-06-29
  • Java多线程同步问题的示例分析
    这篇文章主要介绍Java多线程同步问题的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!简单了解下在操作系统中进程和线程的区别:  进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开...
    99+
    2023-05-30
    java
  • Java多线程 - 线程安全和线程同步解决线程安全问题
    文章目录 线程安全问题线程同步方式一: 同步代码块方式二: 同步方法方式三: Lock锁 线程安全问题 线程安全问题指的是: 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。 举例:...
    99+
    2023-08-20
    java 安全 jvm
  • Java使用线程同步解决线程安全问题详解
    第一种方法:同步代码块: 作用:把出现线程安全的核心代码上锁 原理:每次只能一个线程进入,执行完毕后自行解锁,其他线程才能进来执行 锁对象要求:理论上,锁对象只要对于当前同时执行的线...
    99+
    2024-04-02
  • Java学习之线程同步与线程间通信详解
    目录线程同步的概念同步代码块同步方法线程组线程组的相关方法线程组对象的基本应用线程间的通信线程通信简单应用线程同步的概念 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时...
    99+
    2022-12-27
    Java线程同步 Java线程通信 Java线程
  • C++中的多线程同步问题详解
    C++中的多线程同步问题详解在并发编程中,多线程同步是一个重要的问题。当多个线程同时访问共享资源时,会引发各种问题,如竞态条件(Race Condition)、死锁(Deadlock)和活锁(Livock),这些问题都会导致程序的不确定性和...
    99+
    2023-10-22
    C++ 多线程 同步问题
  • Java同步框架API:如何避免多线程竞争问题?
    在Java多线程编程中,同步是一个必不可少的概念。同步可以保证多个线程按照一定的顺序访问共享资源,避免数据不一致性的问题。Java提供了多种同步框架API,如synchronized、ReentrantLock、Semaphore等,本文...
    99+
    2023-09-05
    同步 框架 api
  • ReentrantLock从源码解析Java多线程同步学习
    目录前言管程管程模型MESA模型主要特点AQS共享变量资源访问方式主要方法队列node节点等待状态ReentrantLock源码分析实例化ReentrantLock加锁A线程加锁成功...
    99+
    2023-05-16
    Java多线程ReentrantLock Java ReentrantLock
  • Java线程同步机制_动力节点Java学院整理
    在之前,已经学习到了线程的创建和状态控制,但是每个线程之间几乎都没有什么太大的联系。可是有的时候,可能存在多个线程多同一个数据进行操作,这样,可能就会引用各种奇怪的问题。现在就来学习多线程对数据访问的控制吧。由于同一进程的多个线程共享同一片...
    99+
    2023-05-31
    java 线程 同步机制
  • mysql主备复制I/O线程不能同步问题
    问题,做完mysql准备复制,为什么备机的I/O线程不能同步? 查看错误日志文件错误原因:由于主机和备机的UUID一致解决方法:修改UUID保证主备机不一致(即修改配置文件/var/lib/mysql/au...
    99+
    2024-04-02
  • C++中的多线程同步问题及解决方法
    C++中的多线程同步问题及解决方法多线程编程是提高程序性能和效率的一种方式,但同时也带来了一系列的同步问题。在多线程编程中,多个线程可能会同时访问和修改共享的数据资源,这可能导致数据的竞争条件、死锁、饥饿等问题。为了避免这些问题,我们需要使...
    99+
    2023-10-22
    多线程 (Multithreading) 同步 (synchronization) 解决方法 (Solution)
  • Spring 中的同步机制能否解决多线程问题?
    Spring是一个流行的Java开发框架,为Java开发者提供了许多便利的功能和工具。然而,当我们在使用Spring框架进行多线程编程时,我们是否需要考虑同步机制来解决多线程问题呢?这是一个值得讨论的问题。 首先,我们需要了解什么是同步机制...
    99+
    2023-09-21
    面试 同步 spring
  • C#中常见的线程同步问题及解决方法
    C#中常见的线程同步问题及解决方法引言:在多线程编程中,线程同步是一个关键的概念。当多个线程同时访问共享资源时,会导致数据不一致或出现竞态条件等问题。本文将介绍C#中常见的线程同步问题,并提供相应的解决方法和示例代码。一、不正确的数据共享当...
    99+
    2023-10-22
    线程同步问题 C#中的线程同步
  • Java并发编程(03):多线程并发访问,同步控制
    本文源码:GitHub·点这里 || GitEE·点这里一、并发问题多线程学习的时候,要面对的第一个复杂问题就是,并发模式下变量的访问,如果不理清楚内在流程和原因,经常会出现这样一个问题:线程处理后的变量值不是自己想要的,可能还会一脸懵的...
    99+
    2023-06-02
  • C#开发中如何处理线程同步和并发访问问题
    C#开发中如何处理线程同步和并发访问问题,需要具体代码示例在C#开发中,线程同步和并发访问问题是一个常见的挑战。由于多个线程可以同时访问和操作共享数据,可能会出现竞态条件和数据不一致的问题。为了解决这些问题,我们可以使用各种同步机制和并发控...
    99+
    2023-10-22
    线程同步 并发访问 C#开发
  • Java线程的锁对象Lock同步问的处理方式
    本篇内容介绍了“Java线程的锁对象Lock同步问的处理方式”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Lock是java.util.co...
    99+
    2023-05-30
    java
  • C++中的多线程同步问题和解决方法概述
    C++中的多线程同步问题和解决方法概述多线程编程是一种并发编程的方式,有助于提高程序的性能和效率。然而,多线程编程也带来了一系列的挑战和问题,其中最突出的是多线程同步问题。本文将概述C++中的多线程同步问题,并介绍几种常见的解决方法。同时,...
    99+
    2023-10-22
    C++多线程同步 多线程问题 同步解决方法
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作