Java多线程之哲学家就餐问题详解

一、题目

教材提供一个哲学家就餐问题的解决方案的框架。本问题要求通过pthreads 互斥锁来实现这个解决方案。
哲学家 首先创建 5 个哲学家,每个用数字 0~4 来标识。每个哲学家作为一个单独的 线程运行。 可使用 Pthreads 创建线程。哲学家在思考和吃饭之间交替。为了模拟这两种活动,请让线程休眠 1 到 3 秒钟。当哲学家想要吃饭时,他调用函数:
pickup_forks(int philosopher _number) 其中,philosopher _number
为想吃饭哲学家的数字。当哲学家吃完后,他调用函数:

return _forks(int philosopher _number)

Pthreads 条件变量 Pthreads 条件变量使用数据类型 pthread_cond_t,采用函数 pthread cond
init()初始化。以下代码创建并初始化条件变量及其关联的互斥锁:

pthread _mutex_ t mutex;
pthread_ cond_ t cond _var;
pthread _mutex_ init(&mutex,NULL);
pthread _cond_ init(&cond _var,NULL);

函数 pthread_cond_wait()用于等待条件变量。下面的代码采用 Pthreads条件变量,说明线程如何等待条件 a==b 变为
true。

pthread _mutex_ lock(&mutex);
while (a != b)
pthread_cond _wait(&mutex, &cond var);
pthread _mutex_ unlock(&mutex);

与条件变量关联的互斥锁在调用pthread_cond_wait()函数之前,应加锁,因为保护条件语句中的数据,避免竞争条件。一旦获得锁,线程可以检查条件。如果条件不成立,然后线程调用 pthread_cond_wait(),传递互斥锁和条件变量作为参数。调用pthread_cond_wait()释放互斥锁,从而允许另一个线程访问共享变量,也可更新其值,以便条件语句的计算结果为真。(为了防止程序错误,重要的是将条件语句放在循环中,以便在被唤醒后重新检查条件。)修改共享数据的线程可以调用 pthread_cond_signal()函数,从而唤醒一个等待条件变量的线程。这个代码如下:

pthread_ mutex_ lock(&mutex);
a = b;
pthread_ cond_ signal(&cond var);
pthread _mutex_ unlock(&mutex);

需要注意的是,调用pthread_cond_signal()不会释放互斥锁。随后调用pthread_mutex_unlock()释放互斥锁。一旦互斥锁被释放,唤醒线程成为互斥锁的所有者,并将控制权返回到对pthread cond wait()的调用。

二、题目解析

题目是书上的哲学家就餐问题,要求用互斥锁解决。
这题的关键在于筷子这个资源的合理使用,书上给出了三种不同的解决思路:

其实三种方法的核心就是为了解决死锁问题。

什么是死锁呢?

用专业点的话说就是:一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象

说白了,就是你拿了我想要拿的资源,我拿了你想要拿的资源,而双方各执一词,导致一直无法解决问题

那我的思路就是:

双方各退一步,当发现我想要的资源不够我完成我所需的事情时,那就把之前拿到的资源放回。这样就不会导致双方互相等待导致死锁的情况。

当然思路不止一种,在极客时间的Java并发编程实战中有讲到死锁发生的条件:

只有以下这四个条件都发生时才会出现死锁:

互斥,共享资源 X 和 Y 只能被一个线程占用;占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;不可抢占,其他线程不能强行抢占线程 T1 占有的资源;循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

如何解决死锁呢?打破任意一个条件即可。

其中,互斥这个条件我们没有办法破坏,因为我们用锁为的就是互斥。不过其他三个条件都是有办法破坏掉的,到底如何做呢?

对于“占用且等待”这个条件,我们可以一次性申请所有的资源,这样就不存在等待了。对于“不可抢占”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占这个条件就破坏掉了。对于“循环等待”这个条件,可以靠按序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后自然就不存在循环了。

好了,回到正题上来,既然我们的思路就是拿不到资源就选择退让,我们可以借用Java里的ReentrantLock类中trylock()方法实现,该方法的作用就是尝试锁住该资源。和lock()方法不同的是,如果该资源已经上锁,那么线程不会阻塞,而是返回flase,表示无法上锁,当然还可以加入参数让它再尝试几秒,比如:

lock.tryLock(1, TimeUnit.SECONDS));

而具体用法就是

if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        ...
    } finally {
        lock.unlock();
    }
}

三、代码实现

package com.dreamchaser.concurrent;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 哲学家就餐问题
 */
public class Proj3_02 {
    static final Lock[] locks=new Lock[5];
    static {
        for (int i=0;i<locks.length;i++){
            locks[i]=new ReentrantLock();
        }
    }

    public static void main(String[] args) {
        Philosopher philosopher0=new Philosopher("张三",1000,0);
        Philosopher philosopher1=new Philosopher("李四",800,1);
        Philosopher philosopher2=new Philosopher("王五",400,2);
        Philosopher philosopher3=new Philosopher("jhl",2000,3);
        Philosopher philosopher4=new Philosopher("ghlcl",2000,4);
        philosopher0.start();
        philosopher1.start();
        philosopher2.start();
        philosopher3.start();
        philosopher4.start();
        //死循环,防止主线程退出导致进程关闭
        while (true){}
    }

    static class Philosopher extends Thread{
        private String name;
        private long time;
        private int num;

        public Philosopher(String name, long time, int num) {
            this.name = name;
            this.time = time;
            this.num = num;
        }

        @Override
        public void run() {
            while (true){
                System.out.println(num+"号哲学家"+name+"正在思考...");
                //模拟思考的过程
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(num+"号哲学家"+name+"饿了,想来吃饭...");
                if (locks[num].tryLock()){
                    try {
                        System.out.println(num+"号哲学家"+name+"拿到了左边的筷子!");
                        if (locks[(num+1)%5].tryLock()){
                            try {
                                System.out.println(num+"号哲学家"+name+"拿到了右边的筷子!");
                                System.out.println(num+"号哲学家"+name+"开始吃饭!");
                                //模拟哲学家吃饭的过程
                                Thread.sleep(time);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            } finally {
                                System.out.println(num+"号哲学家"+name+"放下了右边的筷子!");
                                locks[(num+1)%5].unlock();
                            }
                        }else {
                            System.out.println(num+"号哲学家"+name+"没拿到了右边的筷子!被迫思考...");
                        }
                    }finally {
                        System.out.println(num+"号哲学家"+name+"放下了左边的筷子!");
                        locks[num].unlock();
                    }
                }else {
                    System.out.println(num+"号哲学家"+name+"没拿到了左边的筷子!被迫思考...");
                }
                System.out.println(num+"号哲学家"+name+"思考ing...");
                //模拟思考过程
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

四、运行效果截图

五、结语

以上是我的实现思路。希望我的思路能个各位有所帮助,当然,如果有什么意见或者建议的,欢迎在评论区评论。

到此这篇关于Java多线程之哲学家就餐问题详解的文章就介绍到这了,更多相关Java哲学家就餐详解内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 教你如何使用Java多线程编程LockSupport工具类

    LockSupport类 用于创建锁和其他同步类的基本线程阻塞原语,此类与使用它的每个线程关联一个许可.如果获得许可,将立即返回对park的调用,并在此过程中消耗掉它:否则may会被阻止.调用unpark可使许可证可用(如果尚不可用).(不过与信号量不同,许可证不会累积.最多只能有一个.) 方法park和unpark提供了有效的阻塞和解阻塞线程的方法,这些线程不会遇到导致已弃用的方法Thread.suspend和Thread.resume无法用于以下问题:由于许可,在调用park的一个线程与试图

  • Java实现UDP多线程在线咨询

    本文实例为大家分享了Java实现UDP多线程在线咨询,供大家参考,具体内容如下 1.发送的线程 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import jav

  • Java模拟死锁发生之演绎哲学家进餐问题案例详解

    本文实例讲述了Java模拟死锁发生之演绎哲学家进餐问题.分享给大家供大家参考,具体如下: 一 点睛 常见的死锁形式:当线程1已经占据资源R1,并持有资源R1上的锁,而且还在等待资源R2的锁:而线程2已经占据资源R2,并且持有资源R2上的锁,却正在等待资源R1上的锁.如果两个线程不释放自己占据的资源锁,而且还申请对方资源上的锁,申请不到时只能等待,而且它们只能永远的等待下去. 二 实战 1 代码 public class DeadLockDemo { /** knife锁 */ private s

  • Java多线程之简单模拟售票功能

    一.创建 二.完整代码 package com.ql; import lombok.SneakyThrows; import okhttp3.Call; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; public class Mythread extends Thread { public Mythread(String name)

  • Python实现哲学家就餐问题实例代码

    哲学家就餐问题: 哲学家就餐问题是典型的同步问题,该问题描述的是五个哲学家共用一张圆桌,分别坐在五张椅子上,在圆桌上有五个盘子和五个叉子(如下图),他们的生活方式是交替的进行思考和进餐,思考时不能用餐,用餐时不能思考.平时,一个哲学家进行思考,饥饿时便试图用餐,只有在他同时拿到他的盘子左右两边的两个叉子时才能进餐.进餐完毕后,他会放下叉子继续思考.请写出代码来解决如上的哲学家就餐问题,要求代码返回"当每个哲学家分别需要进食 n 次"时这五位哲学家具体的行为记录. 测试用例: 输入:n

  • 哲学家就餐问题中的JAVA多线程学习

    问题描述:一圆桌前坐着5位哲学家,两个人中间有一只筷子,桌子中央有面条.哲学家思考问题,当饿了的时候拿起左右两只筷子吃饭,必须拿到两只筷子才能吃饭.上述问题会产生死锁的情况,当5个哲学家都拿起自己右手边的筷子,准备拿左手边的筷子时产生死锁现象. 解决办法: 1.添加一个服务生,只有当经过服务生同意之后才能拿筷子,服务生负责避免死锁发生. 2.每个哲学家必须确定自己左右手的筷子都可用的时候,才能同时拿起两只筷子进餐,吃完之后同时放下两只筷子. 3.规定每个哲学家拿筷子时必须拿序号小的那只,这样最后

  • Java commons io包实现多线程同步图片下载入门教程

    目的: 实现多线程同时下载网络图片,入门级. 多线程入门 commons io: 是针对开发IO流功能的工具类库,其中包含了许多可调用的函数. 1.commons io 可直接百度,进入官网直接下载即可 Linux下载tar.gz,window下载.zip. 2.解压commons io ,复制下面的java文件,后在项目中,新建package,我的名为lib,如下,将复制的java文件粘贴到package中,并鼠标右击此文件,点击add as a library即可. 3.代码如下:多线程基础

  • Java多线程之哲学家就餐问题详解

    一.题目 教材提供一个哲学家就餐问题的解决方案的框架.本问题要求通过pthreads 互斥锁来实现这个解决方案. 哲学家 首先创建 5 个哲学家,每个用数字 0~4 来标识.每个哲学家作为一个单独的 线程运行. 可使用 Pthreads 创建线程.哲学家在思考和吃饭之间交替.为了模拟这两种活动,请让线程休眠 1 到 3 秒钟.当哲学家想要吃饭时,他调用函数: pickup_forks(int philosopher _number) 其中,philosopher _number 为想吃饭哲学家的

  • Java多线程中ReentrantLock与Condition详解

    一.ReentrantLock类 1.1什么是reentrantlock java.util.concurrent.lock中的Lock框架是锁定的一个抽象,它允许把锁定的实现作为Java类,而不是作为语言的特性来实现.这就为Lock的多种实现留下了空间,各种实现可能有不同的调度算法.性能特性或者锁定语义.ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,但是添加了类似锁投票.定时锁等候和可中断锁等候的一些特性.此外,它还提供了在激烈争用情况下更

  • java多线程关键字final和static详解

    这篇文章主要介绍了java多线程关键字final和static详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 final关键字 1.final关键字在单线程中的特点: 1)final修饰的静态成员:必须在进行显示初始化或静态代码块赋值,并且仅能赋值一次. 2)final修饰的类成员变量,可以在三个地方进行赋值:显示初始化.构造代码块和构造方法,并且仅能赋值一次. 3)final修饰的局部变量,必须在使用之前进行显示初始化(并不一定要在定义是

  • Java多线程之搞定最后一公里详解

    目录 绪论 一:线程安全问题 1.1 提出问题 1.2 不安全的原因 1.2.1 原子性 1.2.2 代码"优化" 二:如何解决线程不安全的问题 2.1 通过synchronized关键字 2.2 volatile 三:wait和notify关键字 3.1 wait方法 3.2 notify方法 3.3 wait和sleep对比(面试常考) 四:多线程案例 4.1 饿汉模式单线程 4.2 懒汉模式单线程 4.3 懒汉模式多线程低性能版 4.4懒汉模式-多线程版-二次判断-性能高 总结

  • java 多线程与并发之volatile详解分析

    目录 CPU.内存.缓存的关系 CPU缓存 什么是CPU缓存 为什么要有多级CPU Cache Java内存模型(Java Memory Model,JMM) JMM导致的并发安全问题 可见性 原子性 有序性 volatile volatile特性 volatile 的实现原理 总结 CPU.内存.缓存的关系 要理解JMM,要先从计算机底层开始,下面是一份大佬的研究报告 计算机在做一些我们平时的基本操作时,需要的响应时间是不一样的!如果我们计算一次a+b所需要的的时间: CPU读取内存获得a,1

  • Java多线程读写锁ReentrantReadWriteLock类详解

    目录 ReentrantReadWriteLock 读读共享 写写互斥 读写互斥 源码分析 写锁的获取与释放 读锁的获取与释放 参考文献 真实的多线程业务开发中,最常用到的逻辑就是数据的读写,ReentrantLock虽然具有完全互斥排他的效果(即同一时间只有一个线程正在执行lock后面的任务),这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的.所以在JDK中提供了一种读写锁ReentrantReadWriteLock类,使用它可以加快运行效率. 读写锁表示两个锁,一个是读操作相关的锁

  • Java多线程中的Balking模式详解

    目录 1.场景 2.详细说明 3.Balking模式的本质:停止并返回 源代码如下: 总结 1.场景 自动保存功能: 为防止电脑死机,而定期将数据内容保存到文件中的功能. 2.详细说明 当数据内容被修改时,内容才会被保存.即当写入的内容与上次写入的内容一致时,其实就没有必要执行写入操作.也就是说,以”数据内容是否一致”作为守护条件.若数据内容相同,则不执行写入操作,直接返回. 3.Balking模式的本质:停止并返回 如果现在不合适执行该操作,或者没有必要执行该操作,就停止处理,直接返回—-Ba

  • Java多线程同步工具类CountDownLatch详解

    目录 简介 核心方法 CountDownLatch如何使用 CountDownLatch运行流程 运用场景 总结 简介 CountDownLatch是一个多线程同步工具类,在多线程环境中它允许多个线程处于等待状态,直到前面的线程执行结束.从类名上看CountDown既是数量递减的意思,我们可以把它理解为计数器. 核心方法 countDown():计数器递减方法. await():使调用此方法的线程进入等待状态,直到计数器计数为0时主线程才会被唤醒. await(long, TimeUnit):在

  • Java多线程模拟银行系统存钱问题详解

    目录 一.题目描述 二.解题思路 三.代码详解 多学一个知识点 一.题目描述 题目:模拟一个简单的银行系统,使用两个不同的线程向同一个账户存钱. 实现:使用特殊域变量volatile实现同步. 二.解题思路 创建一个类:SynchronizedBankFrame,继承JFrame类 写一个内部类Bank 定义一个account变量,来表示账户. deposit():一个存钱的方法 getAccount():显示账户余额的方法. 写一个内部类Transfer,实现Runnable接口 在run方法

  • Java多线程案例之阻塞队列详解

    目录 一.阻塞队列介绍 1.1阻塞队列特性 1.2阻塞队列的优点 二.生产者消费者模型 2.1阻塞队列对生产者的优化 三.标准库中的阻塞队列 3.1Java提供阻塞队列实现的标准类 3.2Blockingqueue基本使用 四.阻塞队列实现 4.1阻塞队列的代码实现 4.2阻塞队列搭配生产者与消费者的代码实现 一.阻塞队列介绍 1.1阻塞队列特性 阻塞队列特性: 一.安全性 二.产生阻塞效果 阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.阻塞队列能是一种线程安全的数据结构, 并且具有

随机推荐