生产者——消费者问题
生产者-消费者问题是一个著名的进程同步问题。所描述的是:有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将其所生产的产品放入一个缓冲区中;消费者进程可以从一个缓冲区中取走产品去消费。尽管所有生产者进程和消费者进程都是以异步方式运行的,但他们之间必须保持同步,既不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区投放产品。
实验目的
利用C语言或JAVA语言或C++语言(手段不限),验证生产者与消费问题的过程。
实验仪器设备
Eclipse软件
实验步骤和代码分析
1. 设计思想
设计3个区域来分别表示生产者进程、缓冲区、消费者进程,在每个区块行通过小球(产品)位置或数量的变化来表示进程执行的可视化效果,生产者进程区域小球跑完一次,那么就在缓冲区区块小球数量加1个小球,反之消费者进程区域小球跑完一次,那么缓冲区区块的小球数量减1。同时还要记录缓冲区小球个数情况,如果缓冲区没有小球,表示没有产品,则消费者进程无法继续执行,如果缓冲区小球数量达到满的状态,生产者进程也不会继续进行。另外还要体现一个功能就是当没有暂停生产或者暂停消费时,消费进程会唤醒生产进程(缓冲区已满导致生产者进程自动暂停),生产者进程会唤醒消费者进程(缓冲区为空导致消费者进程自动暂停)。
2. 界面设计
使用Java编写初始界面如下所示:
在第1个区域和第2个区域分别添加两个按钮,点击开始才开始执行对应进程,之后按钮变为暂停按钮,需要暂停则点击暂停。
3. 主要代码
(1) 先定义一个小球类,模拟进程进行情况
class Ball { //模拟进程的球
public int x; //表示球心在Panel中的横坐标
public int y; //表示球心在Panel中的纵坐标
public Ball(int x,int y) {
this.x=x;
this.y=y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
(2) 分别定义3个Panel类,继承与JPanel,根据界面设计来画出相应画板,例如生产者进程区域
class Panel1 extends JPanel {
JButton button_start = new JButton("开始生产");
JLabel label1 = new JLabel("姓名:解庚 学号:07060319");
JLabel label2 = new JLabel("生产者进程");
public static Ball ball1 = new Ball(50,45);
public void paint(Graphics g) { //当调用repaint()函数时自动执行此处函数
super.paint(g);
g.setColor(Color.WHITE);
g.fillRect(50, 40, 540, 70); //绘制小球运动的区域
g.setColor(Color.GREEN);
g.fillOval(ball1.x,ball1.y,60,60); //根据小球此时球心坐标绘制小球
}
public Panel1() {
setLayout(null);
button_start.setBounds(670, 50, 100, 40);
label1.setBounds(200,0,250,40);
label2.setBounds(300, 110, 200, 40);
add(button_start);
add(label1);
add(label2);
button_start.addActionListener(new ActionListener() { //为这个按钮添加监听,记录按钮按下情况
@Override
public void actionPerformed(ActionEvent actionEvent) { //如果按钮被按就自动执行此函数
if(BallMove1.flag==true) //修改按钮的记录值
{
BallMove1.flag=false;
button_start.setText("开始生产");
}
else
{
BallMove1.flag=true;
button_start.setText("停止生产");
}
}
});
}
}
(3) 设计3个进程类,用来改变小球运行情况,例如生产者进程
class BallMove1 extends Thread{
public static boolean flag = false; //记录生产者按钮按下情况(按开始生产为true)
public static boolean produceFinish =false; //用来记录生产过程是否完成
public Panel1 panel1;
public Ball b;
public BallMove1(Ball b,Panel1 panel1) {
this.b=b;
this.panel1=panel1;
}
@Override
public void run() {
produceFinish =false;
if(b.x<530) {
panel1.repaint();
b.x+=10;
try {
Thread.sleep(40); //进程执行到此处休眠40毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
produceFinish = true; //一次生产过程完成
b.x = 40; //还原到起点位置
}
}
public boolean getProduceFinish() {
return this.produceFinish;
}
public void setProduceFinish(boolean b) {
this.produceFinish = b;
}
}
(4) 主函数
public static void main(String[] args) {
// TODO Auto-generated method stub
MyFrame frame = new MyFrame();
frame.setDefaultCloseOperation(MyFrame.EXIT_ON_CLOSE); //关闭窗口时结束程序
//三个进程来改变小球运行情况
BallMove1 ballmove1 = new BallMove1(Panel1.ball1,MyFrame.panel1);
BallMove2 ballmove2 = new BallMove2(Panel2.ball2,MyFrame.panel2);
BallMove3 ballmove3 = new BallMove3(Panel3.ball3,MyFrame.panel3);
while(true) { //通过各个参数控制进程的执行
if(ballmove1.flag == true&&(ballmove2.getFillFlag()<9)) {
ballmove1.run();
}
else { //当if条件不满足,同时生产者进程也不可能继续完成生产
ballmove1.setProduceFinish(false);//生产者停止生产或者缓冲区满时要修改此处boolean值,不然当缓冲区满了点击停止生产,后点击开始消费时缓冲区中小球个数不会改变
ballmove1.yield(); //当前进程愿意让出CPU的使用(不接受调度)
}
if(ballmove1.getProduceFinish()==true) {
ballmove2.run(1); //传入参数1表示生产者进程调用
}else {}
if(ballmove3.flag == true&&(ballmove2.getFillFlag()>0)) {
ballmove3.run();
}
else { //当if条件不满足,同时消费者进程也不可能继续完成消费
ballmove3.setBuyFinish(false);//消费者停止消费或者缓冲区空时要修改此处boolean值,不然当缓冲区空了点击停止消费,后点击开始生产时缓冲区中小球个数也不会改变
ballmove3.yield();
}
if(ballmove3.getBuyFinish()==true) {
ballmove2.run(3); //传入参数1表示消费者进程调用
}else {}
}
}
附录程序清单
更多程序代码解释尽在注释
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ProducerAndConsumer {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyFrame frame = new MyFrame();
frame.setDefaultCloseOperation(MyFrame.EXIT_ON_CLOSE); //关闭窗口时结束程序
//三个进程来改变小球运行情况
BallMove1 ballmove1 = new BallMove1(Panel1.ball1,MyFrame.panel1);
BallMove2 ballmove2 = new BallMove2(Panel2.ball2,MyFrame.panel2);
BallMove3 ballmove3 = new BallMove3(Panel3.ball3,MyFrame.panel3);
while(true) { //通过各个参数控制进程的执行
if(ballmove1.flag == true&&(ballmove2.getFillFlag()<9)) {
ballmove1.run();
}
else { //当if条件不满足,同时生产者进程也不可能继续完成生产
ballmove1.setProduceFinish(false);//生产者停止生产或者缓冲区满时要修改此处boolean值,不然当缓冲区满了点击停止生产,后点击开始消费时缓冲区中小球个数不会改变
ballmove1.yield(); //当前进程愿意让出CPU的使用(不接受调度)
}
if(ballmove1.getProduceFinish()==true) {
ballmove2.run(1); //传入参数1表示生产者进程调用
}else {}
if(ballmove3.flag == true&&(ballmove2.getFillFlag()>0)) {
ballmove3.run();
}
else { //当if条件不满足,同时消费者进程也不可能继续完成消费
ballmove3.setBuyFinish(false);//消费者停止消费或者缓冲区空时要修改此处boolean值,不然当缓冲区空了点击停止消费,后点击开始生产时缓冲区中小球个数也不会改变
ballmove3.yield();
}
if(ballmove3.getBuyFinish()==true) {
ballmove2.run(3); //传入参数1表示消费者进程调用
}else {}
}
}
}
class MyFrame extends JFrame{
public static Panel1 panel1 = new Panel1();
public static Panel2 panel2 = new Panel2();
public static Panel3 panel3 = new Panel3();
public MyFrame() {
super("模拟生产者与消费者问题");
setSize(850,500);
setLocationRelativeTo(null);//把窗口位置设置到屏幕中心
Container container=this.getContentPane();
GridLayout gridLayout=new GridLayout(3,1); //设置布局为网格布局3行1列
container.setLayout(gridLayout);
panel1.setBackground(Color.cyan);
panel2.setBackground(Color.GREEN);
panel3.setBackground(Color.CYAN);
container.add(panel1);
container.add(panel2);
container.add(panel3);
setVisible(true);
}
}
class Panel1 extends JPanel {
JButton button_start = new JButton("开始生产");
JLabel label1 = new JLabel("姓名:解庚 学号:07060319");
JLabel label2 = new JLabel("生产者进程");
public static Ball ball1 = new Ball(50,45);
public void paint(Graphics g) { //当调用repaint()函数时自动执行此处函数
super.paint(g);
g.setColor(Color.WHITE);
g.fillRect(50, 40, 540, 70); //绘制小球运动的区域
g.setColor(Color.GREEN);
g.fillOval(ball1.x,ball1.y,60,60); //根据小球此时球心坐标绘制小球
}
public Panel1() {
setLayout(null);
button_start.setBounds(670, 50, 100, 40);
label1.setBounds(200,0,250,40);
label2.setBounds(300, 110, 200, 40);
add(button_start);
add(label1);
add(label2);
button_start.addActionListener(new ActionListener() { //为这个按钮添加监听,记录按钮按下情况
@Override
public void actionPerformed(ActionEvent actionEvent) { //如果按钮被按就自动执行此函数
if(BallMove1.flag==true) //修改按钮的记录值
{
BallMove1.flag=false;
button_start.setText("开始生产");
}
else
{
BallMove1.flag=true;
button_start.setText("停止生产");
}
}
});
}
}
class BallMove1 extends Thread{
public static boolean flag = false; //记录生产者按钮按下情况(按开始生产为true)
public static boolean produceFinish =false; //用来记录生产过程是否完成
public Panel1 panel1;
public Ball b;
public BallMove1(Ball b,Panel1 panel1) {
this.b=b;
this.panel1=panel1;
}
@Override
public void run() {
produceFinish =false;
if(b.x<530) {
panel1.repaint();
b.x+=10;
try {
Thread.sleep(40); //进程执行到此处休眠40毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
produceFinish = true; //一次生产过程完成
b.x = 40; //还原到起点位置
}
}
public boolean getProduceFinish() {
return this.produceFinish;
}
public void setProduceFinish(boolean b) {
this.produceFinish = b;
}
}
class Panel2 extends JPanel{
public static Ball ball2 = new Ball(50,45);
public static int n; //表示仓库有n个商品
JLabel label1 = new JLabel("缓冲区");
public Panel2() {
setLayout(null);
label1.setBounds(310, 110, 200, 40);
add(label1);
}
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.WHITE);
g.fillRect(50, 40, 540, 70);
g.setColor(Color.red);
for(int i= 50;i<=ball2.x;) { //循环画出缓冲区小球个数
g.fillOval(i,ball2.y,60,60);
i=i+60;
}
}
}
class BallMove2 extends Thread{
public static int fillFlag= 1; //记录仓库产品个数,刚开始有1个
public Panel2 panel2;
public Ball b;
public BallMove2(Ball b,Panel2 panel2) {
this.b = b;
this.panel2 = panel2;
}
public int getFillFlag() {
return fillFlag;
}
public void run(int whichThread) { //这里1表示生产,3表示消费
if(whichThread == 1) {
if(b.x<530) {
b.x=b.x+60;
panel2.repaint();
fillFlag=fillFlag+1;
}
}
if(whichThread == 3) {
if(b.x>0) {
panel2.repaint();
b.x=b.x-60;
fillFlag=fillFlag-1;
}
}
}
}
class Panel3 extends JPanel{
JButton button_start = new JButton("开始消费");
public static Ball ball3 = new Ball(50,45);
JLabel label1 = new JLabel("消费者进程");
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.WHITE);
g.fillRect(50, 40, 540, 70);
g.setColor(Color.GREEN);
g.fillOval(ball3.x,ball3.y,60,60);
}
public Panel3() {
setLayout(null);
button_start.setBounds(670, 50, 100, 40);
add(button_start);
label1.setBounds(300, 110, 200, 40);
add(label1);
button_start.addActionListener(new ActionListener() { //为这个按钮添加监听,记录按钮按下情况
@Override
public void actionPerformed(ActionEvent actionEvent) {
if(BallMove3.flag==true)
{
BallMove3.flag=false;
button_start.setText("开始消费");
}
else
{
BallMove3.flag=true;
button_start.setText("停止消费");
}
}
});
}
}
class BallMove3 extends Thread{
public static boolean flag = false; //记录生产者按钮按下情况(按开始生产为true)
public static boolean buyFinish =false; //用来购入过程是否完成
public Panel3 panel3;
public Ball b;
public BallMove3(Ball b,Panel3 panel3) {
this.b=b;
this.panel3=panel3;
}
@Override
public void run() {
if(b.x<530) {
buyFinish = false;
panel3.repaint();
b.x+=10;
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
buyFinish = true;
b.x = 40; //还原到起点位置
}
}
public boolean getBuyFinish() {
return this.buyFinish;
}
public void setBuyFinish(boolean b) {
this.buyFinish = b;
}
}
class Ball { //模拟进程的球
public int x; //表示球心在Panel中的横坐标
public int y; //表示球心在Panel中的纵坐标
public Ball(int x,int y) {
this.x=x;
this.y=y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
部分效果截图
同时生产和同时消费
缓冲区满,生产者进程停止,如果没有点击停止生产按钮就可以被消费者进程唤醒(当消费者进程完成一次,缓冲区中有了空闲位置,那么生产者进程自动开始)
缓冲区为空,消费者进程停止,如果没有点击停止消费按钮就可以被生产者进程唤醒(当生产者进程完成一次,缓冲区中有了一个产品,那么消费者进程自动开始)