- 本程序模拟实现了“生产者-消费者”问题的解决过程,用图形界面动态演示了P、V操作过程以及生产者、消费者进程之间的工作流程。
- 本程序使用的算法是典型的P、V操作使用信号量解决“生产者-消费者”问题。
本程序在界面上使用了Java的swing接口函数,用矩形条表示生产者进程中待生产的产品,并设置了三个分区分别表示生产者进程待生产的产品、公共缓冲池中已生产的产品和消费者进程已消费的产品,以动画的效果动态演示了待生产产品变成消费者进程中已消费产品的过程,以及在这一过程中生产者进程和消费者进程协调工作的过程。 - 在程序运行过程中使用了两个生产者线程和两个消费者线程并发工作,并使用了线程随机休眠的策略,即每个线程在完成一次生产过程或消费过程后随机休眠1至10秒钟。这一策略能保证生产者和消费者之间的运行顺序被打破,从而产生生产产品和消费产品之间的矛盾(即没有产品可消费的情况下消费者试图向公共缓冲池取产品消费、公共缓冲池里的产品已满的情况下生产者试图生产产品放入缓冲池)。因为生产者生产产品和消费者消费产品都是随机的,所以产生的矛盾也是不可预知的,在这种情况下,才能检验所使用的算法是否健壮高效。而本程序正是基于这种思想设计出来的,用来模拟生产者消费者问题的解决过程。
- 本程序在运行时提供友好的交互界面,且操作简单,在模拟过程中各种情况有相应文字提示,并伴有相应的图像变化!并内嵌帮助文档!
程序结构:
Main 运行即可
package frame;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class CenterPanel extends JPanel{
/**
* @Fields serialVersionUID :
*/
private static final long serialVersionUID = 1L;
public CenterPanel() {
this.setBackground(Color.WHITE);
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.gray);
//画出生产者队列中的所有产品(不是线程)
for(int i=0;i<MyFrame.proList.size();i++) {
g.fill3DRect(35, i*20+5, 200, 20, true);
g.drawString(MyFrame.proList.get(i), 235, i*20+15);
}
if(MyFrame.isFull()) {
//若公共缓冲池已满,将公共缓冲池里的缓冲区变为黄色
g.setColor(Color.YELLOW);
}
//画出公共缓冲池中所有缓冲区(不是线程)
for(int i=0;i<MyFrame.comList.size();i++) {
g.fill3DRect(285, i*20+5, 200, 20, true);
g.drawString(MyFrame.comList.get(i), 485, i*20+15);
}
//将画笔颜色调回灰色
g.setColor(Color.gray);
// 画出消费者队列中所有已取得的任务(不是线程)
for (int i = 0; i < MyFrame.conList.size(); i++) {
g.fill3DRect(535, i * 20 + 5, 200, 20, true);
g.drawString(MyFrame.conList.get(i), 735, i*20+15);
}
if(MyFrame.isEmpty()) {
//若公共缓冲池为空
g.setColor(Color.red);
g.fill3DRect(285, 5, 200, 200, true);
g.setColor(Color.black);
g.drawString("Empty", 385, 100);
}
}
}
package frame;
import java.awt.Font;
import java.util.LinkedList;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.border.TitledBorder;
import main.Main;
/**
* @ClassName: MyFrame
* @Description: 设计交互界面
* @author OuO
* @date 2020年5月1日 上午9:54:42
*/
public class MyFrame extends JFrame{
/**
* @Fields serialVersionUID :
*/
private static final long serialVersionUID = 1L;
public static JPanel topPanel, lowPanel, lablePanel;
public static JTextArea textArea;
public static JScrollPane textScroll;
public static JButton startButton, helpButton;
public static JComboBox<String> jcb;
public static CenterPanel centerPanel;
/**
* @Fields proList : 生产者待生产产品链表
*/
/**
* @Fields conList : 消费者已消费产品链表
*/
/**
* @Fields comList : 公共缓冲池链表
*/
public static LinkedList<String> proList,conList,comList;
/**
* @Fields full : 公共缓冲池是否已满
*/
public static boolean full=false;
/**
* @Fields empty : 公共缓冲池是否为空
*/
public static boolean empty=false;
Main m=new Main();
//初始化链表
static {
proList=new LinkedList<>();
conList=new LinkedList<>();
comList=new LinkedList<>();
}
// public Cipher cipher=new Cipher();
public MyFrame() {
//设置文本框字体
Font textFont=new Font("宋体", Font.PLAIN, 20);
//设置左上角的组件布局
topPanel=setTopLeftPanel();
// 定义右上角(即文字显示演示过程)组件部分
textArea = new JTextArea(20, 10);
textArea.setEditable(false);
textScroll = new JScrollPane();
textScroll.setViewportView(textArea);
textScroll.setColumnHeaderView(new JLabel("文字显示"));
textArea.setFont(textFont);
//定义中间(即显示框)部分组件
lablePanel=setLablePanel();
centerPanel=new CenterPanel();
//定义底部按钮组件
lowPanel=new JPanel();
lowPanel.setLayout(null);
startButton=new JButton("开始");
//添加监听
startButton.addActionListener(m);
helpButton=new JButton("帮助");
helpButton.addActionListener(m);
startButton.setBounds(210,1,70,35);
helpButton.setBounds(450,1,70,35);
lowPanel.add(startButton);
lowPanel.add(helpButton);
//将所有组件添加到JFrame里
this.setLayout(null);
topPanel.setBounds(20, 10, 350, 170);
textScroll.setBounds(400, 10, 350, 170);
lablePanel.setBounds(0,190,770,25);
centerPanel.setBounds(0,220,770,350);
lowPanel.setBounds(20,580,750,40);
this.add(topPanel);
this.add(textScroll);
this.add(lablePanel);
this.add(centerPanel);
this.add(lowPanel);
this.setTitle("生产者-消费者 by GodOuO") ;
setSize(770, 680);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setLocation(200,200);
}
/**
* @Title: setTopLeftPanel
* @Description: // 定义左上角组件部分
* @return void 返回类型
* @throws
*/
private JPanel setTopLeftPanel() {
JLabel numberLabel=new JLabel("公共缓冲池最大缓冲区数量");
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createTitledBorder(null, "选择", TitledBorder.CENTER, TitledBorder.TOP));
// 重置topPanel1的布局
panel.setLayout(null);
JPanel panel1 = new JPanel();
panel1.setLayout(null);
JPanel panel2 = new JPanel();
panel2.setLayout(null);
//下拉框
String []numberStr= {
"1","2","10"};
jcb=new JComboBox<String>(numberStr);
// 设置各组件在panel里的位置
numberLabel.setBounds(70, 20, 200, 20);
panel1.add(numberLabel);
jcb.setBounds(70, 10, 150, 20);
panel2.add(jcb);
panel1.setBounds(30, 15, 300, 49);
panel2.setBounds(30, 70, 300, 90);
panel.add(panel1);
panel.add(panel2);
return panel;
}
/**
* @Title: setLablePanel
* @Description: 定义图形显示区的组件
* @return
* @return JPanel 返回类型
* @throws
*/
private JPanel setLablePanel() {
JPanel panel=new JPanel();
panel.setLayout(null);
JLabel proLabel,comLabel,conLabel;
proLabel=new JLabel("待生产产品");
comLabel=new JLabel("公共缓冲池(已生产产品)");
conLabel=new JLabel("已消费产品");
//设置布局
proLabel.setBounds(35,5,200,20);
comLabel.setBounds(285,5,200,20);
conLabel.setBounds(535,5,200,20);
panel.add(proLabel);
panel.add(comLabel);
panel.add(conLabel);
return panel;
}
public synchronized static boolean isFull() {
return full;
}
public synchronized static void setFull(boolean f) {
full = f;
}
public synchronized static boolean isEmpty() {
return empty;
}
public synchronized static void setEmpty(boolean e) {
empty = e;
}
}
package main;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import javax.swing.JOptionPane;
import frame.MyFrame;
public class Main implements ActionListener{
static MyFrame myFrame;
static ProducerConsumer producerConsumer;
public static void main(String[] args) {
myFrame=new MyFrame();
new ProducerConsumer();
}
/* (non-Javadoc)
* Title: actionPerformed
* Description: 监听事件,点击按钮时做出相应反应
* @param e
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==MyFrame.startButton) {
//点击开始按钮
//将显示框清空
MyFrame.textArea.setText("");
util.clear();
String numbers=(String) MyFrame.jcb.getSelectedItem();
producerConsumer=new ProducerConsumer();
producerConsumer.setMAX(Integer.valueOf(numbers));
producerConsumer.run();
}
if(e.getSource()==MyFrame.helpButton) {
//点击按钮
File file=new File("./source/help.text");
try {
new MyHelp().showHelpWin(file);
} catch (Exception e1) {
e1.printStackTrace();
JOptionPane.showMessageDialog(myFrame, "无法打开帮助文档,请联系 GodOuO",
"帮助", JOptionPane.PLAIN_MESSAGE);
}
}
}
}
package main;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
/**
* @ClassName: MyHelp
* @Description: 帮助窗口
* @author OuO
* @date 2020年5月1日 上午10:15:50
*/
public class MyHelp extends JFrame{
/**
* @Fields serialVersionUID :
*/
private static final long serialVersionUID = 1L;
JTextArea textArea = null;
JScrollPane jsp = null;
/**
* @Title: showHelpWin
* @Description: 点击帮助按钮时弹出帮助窗口
* @param file 帮助文档
* @throws Exception
* @return void 返回类型
* @throws
*/
public void showHelpWin(File file) throws Exception {
this.setVisible(true);
this.setSize(580, 300);
this.setLocation(300,250);
textArea = new JTextArea();
jsp = new JScrollPane(textArea);
textArea.setText(null);
textArea.setEditable(false);
//读取文件
InputStream is=MyHelp.class.getClassLoader().getResourceAsStream("help.txt");
InputStreamReader isr = new InputStreamReader(is, "UTF-8");
BufferedReader buf = new BufferedReader(isr);
String str = null;
while ((str = buf.readLine()) != null) {
textArea.append(str);
textArea.append("\r\n");
}
add(jsp);
buf.close();
}
}
package main;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import frame.MyFrame;
public class ProducerConsumer extends Thread{
/**
* @Fields MAX : 缓冲区的数量上限
*/
private int MAX = 1;
/**
* @Fields lock : Lock对象,为临界区加锁和释放
*/
private final Lock lock = new ReentrantLock();
/**
* @Fields full : 缓冲池内满缓冲区
*/
private final Condition full = lock.newCondition();
/**
* @Fields empty : 缓冲区内空缓冲区
*/
private final Condition empty = lock.newCondition();
/**
* @Fields numbers : 生产者队列待生产的产品数
*/
private int numbers=100;
public ProducerConsumer() {
for(int i=0;i<numbers;i++) {
MyFrame.proList.add(new String(String.valueOf(i)));
}
}
@Override
public void run() {
new Producer().start();
new Consumer().start();
new Producer().start();
new Consumer().start();
}
public synchronized int getNumbers() {
return numbers;
}
public synchronized int getMAX() {
return MAX;
}
public synchronized void setMAX(int mAX) {
MAX = mAX;
}
public synchronized void lessNumber() {
this.numbers=this.numbers-1;
}
/**
* @ClassName: Producer
* @Description: 生产者进程
* @author OuO
* @date 2020年5月1日 下午2:15:39
*/
class Producer extends Thread {
/* (non-Javadoc)
* Title: run
* Description: 重写run方法
* @see java.lang.Thread#run()
*/
@Override
public void run() {
while (getNumbers()>=1) {
//加锁
lock.lock();
try {
//线程停止3秒是为了让程序走的慢一些,不至于一眨眼就结束了
sleep(3000);
if (MyFrame.comList.size() == getMAX()) {
//若公共缓冲池已满,将当前进程加入等待队列
//直到被唤醒:full.signal
util.showInfo("警告:Full!\n生产者受阻\n");
MyFrame.setFull(true);
//刷新画面
MyFrame.centerPanel.repaint();
//当前进程进入等待队列
full.await();
}
String str = MyFrame.proList.removeLast();
if (MyFrame.comList.add(str)) {
//若object对象成功添加进队列comList
//说明生产者进程成功生产一个产品并送入公共缓冲池
util.showInfo("生产者: "+str+"\n");
MyFrame.setEmpty(false);
MyFrame.centerPanel.repaint();
//唤醒等待的消费者进程
empty.signal();
}
} catch (InterruptedException ie) {
//捕获到异常说明进程被异常中断
util.showInfo("生产异常终止!\n");
} finally {
//次数减一
lessNumber();
//释放锁
lock.unlock();
try {
//线程随机暂停几秒是为了让生产者和消费者之间的运行产生冲突
//从而达到演示的目的
sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
/**
* @ClassName: Consumer
* @Description: 消费者进程
* @author OuO
* @date 2020年5月1日 下午2:35:56
*/
class Consumer extends Thread {
/* (non-Javadoc)
* Title: run
* Description: 重写run方法
* @see java.lang.Thread#run()
*/
@Override
public void run() {
while (!(getNumbers()==0 && MyFrame.comList.size()==0)) {
//加锁
lock.lock();
try {
//线程停止2秒是为了让程序走的慢一些,不至于一眨眼就结束了
sleep(2000);
if (MyFrame.comList.size() == 0) {
//若comList长度为0说明公共缓冲池里没有没有缓冲区可用
//即缓冲区为空,没有产品可取
util.showInfo("警告: Empty!\n消费者受阻!\n");
MyFrame.setEmpty(true);
//刷新画面
MyFrame.centerPanel.repaint();
//将当前进程放入等待队列直到被唤醒:empty.signal()
empty.await();
}
//消费者进程成功将产品从缓冲池取出
String str = MyFrame.comList.removeLast();
if(MyFrame.conList.add(str)) {
util.showInfo("消费者: " + str+"\n");
MyFrame.setFull(false);
MyFrame.centerPanel.repaint();
//唤醒在等待的生产者进程
full.signal();
}
} catch (InterruptedException ie) {
util.showInfo("消费异常终止!\n");
} finally {
//释放锁
lock.unlock();
try {
//线程随机暂停几秒是为了让生产者和消费者之间的运行产生冲突
//从而达到演示的目的
sleep(new Random().nextInt(8000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
util.showInfo("结束");
}
}
}
package main;
import frame.MyFrame;
public class util {
/**
* @Title: showInfo
* @Description: 将字符串添加到显示框显示
* @param string
* @return void 返回类型
* @throws
*/
public synchronized static void showInfo(String string) {
//将字符串添加到显示框
MyFrame.textArea.append(string);
//显示框滚动条自动跟随文本最后一行
MyFrame.textArea.setCaretPosition(MyFrame.textArea.getDocument().getLength());
}
public static void clear() {
MyFrame.proList.clear();
MyFrame.comList.clear();
MyFrame.conList.clear();
MyFrame.setEmpty(false);
MyFrame.setFull(false);
}
}
Ps:help.txt可自行编写插入程序中!