多线程处理哲学家就餐问题(GUI动态演示)

2024-01-24 16:10

本文主要是介绍多线程处理哲学家就餐问题(GUI动态演示),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

问题描述:

餐桌前坐着5位哲学家,两个人中间有一只筷子,桌子中央有面条。哲学家思考问题,当饿了的时候拿起左右两只筷子吃饭,必须同时拿到两只筷子才能吃饭。当5个哲学家都拿起自己右手边的筷子,准备拿左手边的筷子时可能产生死锁现象。

为了解决互斥问题,必须将筷子资源作为同步资源。在同一时刻,对于一个筷子对象,只能由一个哲学家(线程)对其进行访问。由于Java程序语言提供了同步机制,可以轻松解决线程互斥问题。

算法设计思路:将哲学家、筷子、房间分别定义为三个独立的类。其中,哲学家继承自线程,

拥有think()和eat()两个同步方法。筷子属于共享资源,定义takeup()和putdown()为PV操作。

算法的伪代码如下:

var chopstick:array[0..4] of semaphore(:=1);i:integer;room:semaphore(:=4);
procedure philosopher(i:integer, chopstick left, chopstick right)
begin repeatthink();room.enter();left.takeup();right.takeup();eat();right.putdown();left.putdown();room.exit();forever;
end;
begin(*main)parbeginphilosopher(1).start();philosopher(2).start();philosopher(3).start();philosopher(4).start();philosopher(5).start();parend
end.

1. 定义筷子类。筷子属于同步资源,被持有者持有,同时被索取者竞争。因此,取筷子和放筷子的操作必须作好同步。

public class Chopstick
{private boolean avilable;	//筷子是否可用标记private Philosopher taker;	//索取者private Philosopher  owner;//占有者private Dinner.DinnerTable table;public Chopstick(int id, Dinner.DinnerTable table) {this.table = table;avilable = true;}//取筷子,相当于P操作public synchronized void takeup(Philosopher taker) {this.setTaker(taker);while (!avilable) {try {//System.out.println("哲学家等待另一根筷子");taker.setFace(2);wait();}catch (InterruptedException e) {}}avilable = false;this.setOwner(taker);table.repaint();}//放下筷子,相当于V操作public synchronized void putdown() {avilable = true;this.setOwner(null);notify();  //唤醒其他等待的线程table.repaint();}public int getOwnerId(){return this.getOwner().getPersonId();}public Philosopher getTaker() {return taker;}public void setTaker(Philosopher taker) {this.taker = taker;}public Philosopher getOwner() {return owner;}public void setOwner(Philosopher owner) {this.owner = owner;}
}

2.定义哲学家类。哲学家参与资源的竞争,申明为一个线程,其run()方法为一个死循环操作,

不断切换思考和吃饭两个动作(思考和吃饭设置一个随机时间)。

import java.util.Random;import javax.swing.ImageIcon;public  class Philosopher extends Thread
{public static final  byte STATUS_OF_NORMAL  = 0;		//初始表情public static final byte STATUS_OF_THINK =1;			//思考表情public static final byte STATUS_OF_HUNGRY = 2;		//饥饿表情public static final byte STATUS_OF_EAT = 3;			//就餐表情private Room room;private Chopstick left, right;private int personId;private ImageIcon init, eat, think, hungry;private ImageIcon currentFace;private Random rand = new Random();		//随机休眠时间public Philosopher(Room room, int id, Chopstick left, Chopstick right) {this.room = room;this.personId = id;this.left = left;this.right = right;eat = new ImageIcon("eat.png");think = new ImageIcon("think.png");hungry = new ImageIcon("hungry.png");init = new ImageIcon("init.png");currentFace = init;setFace(Philosopher.STATUS_OF_NORMAL);}public void eat() {left.takeup(Philosopher.this);right.takeup(Philosopher.this);setFace(Philosopher.STATUS_OF_EAT);//  System.out.println("哲学家" + this.philoId + "正在用餐");}public void think() {left.putdown();right.putdown();setFace(Philosopher.STATUS_OF_THINK);//  System.out.println("哲学家" + this.philoId + "正在思考");}/***  设置表情图片*/public void setFace(int faceId) {switch (faceId) {case Philosopher.STATUS_OF_NORMAL:currentFace = init;break;case Philosopher.STATUS_OF_THINK:currentFace = think;break;case Philosopher.STATUS_OF_HUNGRY:currentFace = hungry;break;case Philosopher.STATUS_OF_EAT:currentFace = eat;break;}}public ImageIcon getFace() {return currentFace;}public void run() {while (true) {think();//哲学家思考try {sleep((int) ((rand.nextFloat() + 0.5) * 3000));}catch (InterruptedException e) {}
//			room.enter();//进入房间eat();//哲学家就餐try {sleep((int) ((rand.nextFloat() + 0.5) * 4000));}catch (InterruptedException e) {}
//			room.exit();//离开房间}}public int getPersonId() {return personId;}public void setPersonId(int personId) {this.personId = personId;}
}

3. 用Swing实现图形界面,将界面和业务逻辑代码分开。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;public class Dinner extends JFrame
{private static final int personCount = 20;	//哲学家数量private static Room room = new Room(personCount - 1);//房间锁变量,用于控制同时就餐的人数ddprivate JButton startBtn = new JButton("开始");private JButton stopBtn = new JButton("暂停");private JButton continueBtn = new JButton("继续");private JButton endBtn = new JButton("结束");private boolean started = false;private boolean stopped = false;final static Chopstick[] chopstick = new Chopstick[personCount];final static Philosopher[] philos = new Philosopher[personCount];ImageIcon normalFace, eatFace, thinkFace, hungryFace;  //表情图片// 因Table(餐桌)类与Dinner类联系较为紧密,故将其作为内部类,方便消息的传递class DinnerTable extends JPanel{private int counts;private int r1 = 20;private int CHOPS_LENGTH = 35;	//筷子的长度private int DISTANCE = 100;		//筷子与人的距离private int VERTICAL_DIS = -20;		//表情与名字的距离private int r2 = r1 + CHOPS_LENGTH;private int r3 = r2 + DISTANCE;private int delta;private int delta0 = -18;public DinnerTable(int counts) {setOpaque(false);this.setPreferredSize(new Dimension(550, 400));this.counts = counts;this.delta = 360 / counts;}public void paintComponent(Graphics page) {super.paintComponent(page);int x1, y1, x2, y2, x3, y3;page.fillOval(200, 150, 150, 150);//画餐桌上的筷子for (int i = 0; i < counts; i++) {page.setColor(Color.YELLOW);//根据坐标系 x = r*cos(delta) , y = r*sin(delta) 计算点x1 = 275 + (int) (r1 * Math.cos(((delta * i)+ delta0*(-5)  ) * Math.PI / 180));y1 = 225 + (int) (r1 * Math.sin(((delta * i) + delta0*(-5)  ) * Math.PI / 180));x2 = 275 + (int) (r2 * Math.cos(((delta * i) + delta0 *(-5) ) * Math.PI / 180));y2 = 225 + (int) (r2 * Math.sin(((delta * i) + delta0 *(-5) ) * Math.PI / 180));x3 = 275 + (int) (r3 * Math.cos(((delta * i) + delta0 ) * Math.PI / 180));y3 = 225 + (int) (r3 * Math.sin(((delta * i) + delta0 ) * Math.PI / 180));if (chopstick[i].getOwner() != null && philos[i].getPersonId() == chopstick[i].getOwnerId()) {//检查哲学家是否拿到左边筷子page.drawLine(x3 - 20, y3, x3 - 20, y3 + CHOPS_LENGTH);}if (chopstick[(i + 1) % personCount].getOwner() != null && philos[i].getPersonId()  == chopstick[(i + 1) % personCount].getOwnerId() ) {//检查哲学家是否拿到右边筷子page.drawLine(x3 + 45, y3, x3 + 45, y3 + CHOPS_LENGTH);}if (chopstick[i].getOwner() == null) {//筷子在餐桌上page.drawLine(x1, y1, x2, y2);}philos[i].getFace().paintIcon(this, page, x3, y3);   //画表情page.setColor(Color.BLUE);page.drawString("哲学家" + (i + 1), x3 - 5, y3 + VERTICAL_DIS);//画名字}}}/***  设置窗口居中*/private void putUIInCenter(){int windowWidth = this.getWidth();                 	 	   //获得窗口宽int windowHeight = this.getHeight();                 	  //获得窗口高Toolkit kit = Toolkit.getDefaultToolkit();             	 //定义工具包Dimension screenSize = kit.getScreenSize();             //获取屏幕的尺寸int screenWidth = screenSize.width;                     	//获取屏幕的宽int screenHeight = screenSize.height;                  	 //获取屏幕的高this.setLocation(screenWidth / 2 - windowWidth / 2, screenHeight / 2 - windowHeight / 2);}public Dinner() {setTitle("哲学家就餐");setPreferredSize(new Dimension(600, 650));setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.pack();setVisible(true);setResizable(false);putUIInCenter();normalFace = new ImageIcon("init.png");eatFace = new ImageIcon("eat.png");thinkFace= new ImageIcon("think.png");hungryFace = new ImageIcon("hungry.png");JPanel btnPanel = new JPanel();btnPanel.setBounds(160, 20, 300, 200);btnPanel.setOpaque(false);btnPanel.setLayout(new FlowLayout());btnPanel.add(startBtn);btnPanel.add(stopBtn);btnPanel.add(continueBtn);btnPanel.add(endBtn);//各种按钮增加点击事件startBtn.addActionListener(new startListener());continueBtn.addActionListener(new continueListener());stopBtn.addActionListener(new stopListener());endBtn.addActionListener(new endListener());JPanel mainPanel = new JPanel();mainPanel.setLayout(null);mainPanel.setBackground(Color.black);mainPanel.add(btnPanel);DinnerTable table = new DinnerTable(personCount);table.setBounds(20, 50, 500, 420);for (int i = 0; i < chopstick.length; i++) {chopstick[i] = new Chopstick(i, table);}for (int i = 0; i < philos.length; i++) {philos[i] = new Philosopher(room, i,chopstick[i], chopstick[(i + 1) % personCount]);}mainPanel.add(table);JPanel descipPanel = new JPanel();descipPanel.setBounds(20, 480, 200, 200);descipPanel.setOpaque(false);descipPanel.setForeground(Color.red);descipPanel.setLayout(new BoxLayout(descipPanel, BoxLayout.Y_AXIS));JLabel label0 = new JLabel("哲学家就餐问题(信号量解决)");label0.setForeground(Color.red);JLabel label1 = new JLabel("初始状态的哲学家", normalFace, JLabel.RIGHT);label1.setForeground(Color.red);JLabel label2 = new JLabel("思考状态的哲学家", thinkFace, JLabel.RIGHT);label2.setForeground(Color.red);JLabel label3 = new JLabel("饥饿状态的哲学家", hungryFace, JLabel.RIGHT);label3.setForeground(Color.red);JLabel label4 = new JLabel("正在吃饭的哲学家", eatFace, JLabel.RIGHT);label4.setForeground(Color.red);descipPanel.add(label0);descipPanel.add(label1);descipPanel.add(label2);descipPanel.add(label3);descipPanel.add(label4);mainPanel.add(descipPanel);getContentPane().add(mainPanel);}private class startListener implements ActionListener{public void actionPerformed(ActionEvent evt) {if (!started) {started=true;for (int i = 0; i < personCount; i++) {philos[i].start();}}}}private class stopListener implements ActionListener{public void actionPerformed(ActionEvent evt) {for (int i = 0; i < personCount; i++) {philos[i].suspend();}stopped = true;}}private class continueListener implements ActionListener{public void actionPerformed(ActionEvent evt) {if (stopped) {for (int i = 0; i < personCount; i++) {philos[i].resume();}stopped = false;}}}private class endListener implements ActionListener{public void actionPerformed(ActionEvent evt) {System.exit(1);}}public static void main(String[] args) {new Dinner();}
}

4.程序运行结果如下:

 

5.若修改Dinner类设置的常量 personCount为10,重新运行程序,如下:

6.若继续添加限制,要求在一时刻只能有4个哲学家就餐,则可以再添加另外一个信号量(比如房间资源)。哲学家在竞争筷子之前,必须先进入一个房间才能参与下一步竞争。房间类的设计如下:

/** 限制多个哲学家同时就餐,增设一间房间, 哲学家在准备就餐之前,必须先进入该房间, 若人数超过房间容量,则阻塞该线程*/public class Room
{private static int capacity;   //房间的容量上限public Room(int capacity) {this.capacity = capacity;}public synchronized void enter() {while (capacity == 0) {try {wait();}catch (InterruptedException e) {}}capacity--;}public synchronized void exit() {capacity++;notify();}
}

同时,设置房间上限(Dinner类的Room字段的初始化)

private static Room room = new Room(4);//房间锁变量,用于控制同时就餐的人数

修改哲学家线程的run()方法,竞争筷子资源前先进入一个房间,放下筷子的时候需要退出房间,代码如下:

public void run() {while (true) {think();//哲学家思考try {sleep((int) ((rand.nextFloat() + 0.5) * 3000));}catch (InterruptedException e) {}room.enter();//进入房间eat();//哲学家就餐try {sleep((int) ((rand.nextFloat() + 0.5) * 4000));}catch (InterruptedException e) {}room.exit();//离开房间}}

可以观察到,在同一时刻最多只有4个哲学家在就餐

 

 

 

 

 

 

这篇关于多线程处理哲学家就餐问题(GUI动态演示)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/640225

相关文章

SpringBoot结合Docker进行容器化处理指南

《SpringBoot结合Docker进行容器化处理指南》在当今快速发展的软件工程领域,SpringBoot和Docker已经成为现代Java开发者的必备工具,本文将深入讲解如何将一个SpringBo... 目录前言一、为什么选择 Spring Bootjavascript + docker1. 快速部署与

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

一文详解SpringBoot中控制器的动态注册与卸载

《一文详解SpringBoot中控制器的动态注册与卸载》在项目开发中,通过动态注册和卸载控制器功能,可以根据业务场景和项目需要实现功能的动态增加、删除,提高系统的灵活性和可扩展性,下面我们就来看看Sp... 目录项目结构1. 创建 Spring Boot 启动类2. 创建一个测试控制器3. 创建动态控制器注

Python使用vllm处理多模态数据的预处理技巧

《Python使用vllm处理多模态数据的预处理技巧》本文深入探讨了在Python环境下使用vLLM处理多模态数据的预处理技巧,我们将从基础概念出发,详细讲解文本、图像、音频等多模态数据的预处理方法,... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

Spring Boot @RestControllerAdvice全局异常处理最佳实践

《SpringBoot@RestControllerAdvice全局异常处理最佳实践》本文详解SpringBoot中通过@RestControllerAdvice实现全局异常处理,强调代码复用、统... 目录前言一、为什么要使用全局异常处理?二、核心注解解析1. @RestControllerAdvice2

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

Redis出现中文乱码的问题及解决

《Redis出现中文乱码的问题及解决》:本文主要介绍Redis出现中文乱码的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 问题的产生2China编程. 问题的解决redihttp://www.chinasem.cns数据进制问题的解决中文乱码问题解决总结

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决