基于Java Swing的生产者消费者多道程序设计

基于Java Swing的生产者消费者多道程序设计

一、介绍

最近操作系统布置了一次多道同步互斥程序编程设计体验的作业,抱着水一水心态的我试了一下用GUI界面实现生产者消费者问题的模拟,发现根本做不出来,同专业的同学有用VS MFC做的,有用C#做的,也有人用Eclipse做的Web项目,还有用JAVA AWT做的,可能只有我用Java Swing做的。后来才知道AWT有插件,才明白他们不懂我手动排版的痛,太可怕了。
言归正传,这次项目设计也用了一周的时间,希望能给大家有一些感悟。

二、项目结构图

基于Java Swing的生产者消费者多道程序设计_第1张图片
基于Java Swing的生产者消费者多道程序设计_第2张图片

三、代码介绍

1. 主类PC

这个类的构造函数主要完成大的控件的添加和摆放,完成整个窗体轮廓的构造,具体的实现部分将在后面介绍。

// An highlighted block
public PC() {
		JFrame f = new JFrame("生产者消费者模拟");
		Font font = new Font("楷体", Font.PLAIN, 16);
		JMenuBar menuBar = new JMenuBar();
		JMenu menuFile = new JMenu("文件");
		JMenu menuHelp = new JMenu("帮助");
		JMenuItem menuFileExit = new JMenuItem("退出");
		JMenuItem menuHelpAbout = new JMenuItem("关于");
		JMenuItem menuHelpIndution = new JMenuItem("介绍");
		
		// mp.setVisible(true);

		Content content = new Content();
		f.add("Center", content);
		content.setSize(300, 500);
		Control control = new Control(content);
		f.add("North", control);
		
	}
2. JPanel Content

JPanel是Java Swing中的面板容器,可以容纳各种各样的控件,但是因为它是中间层容器,所以需要依附顶层容器JFrame生存。JPanel的功能十分强大,除了常用的一些方法外,在这次开发中最有用的就是paint()和repaint(),对Paint函数重写,并对界面进行不断的刷新,可以实现简单动画的效果,相信如果用Swing做小游戏,这个功能应该能派上很大的用场。

class Content extends JPanel {// content是主显示部分
	JTextArea ta;
	Image image;
	Image imageP;
	Image imageC;
	public Content() {
		setLayout(null);
		Font f = new Font("楷体", Font.PLAIN, 16);
		this.setOpaque(false);
		ta = new JTextArea();
		JScrollPane sp = new JScrollPane(ta);
		add(sp);
		sp.setBounds(new Rectangle(380, 50, 400, 350));
		ta.setLineWrap(true);
		producer=new JLabel("生产者");
		producer.setBounds(15, 40, 50, 50);
		producer.setFont(f);
		add(producer);
		consumer=new JLabel("消费者");
		consumer.setBounds(270, 40, 50, 50);
		consumer.setFont(f);
		add(consumer);
		buffer=new JLabel("缓冲区");
		buffer.setBounds(150, 40, 50, 50);
		buffer.setFont(f);
		add(buffer);
		
		JLabel lable=new JLabel(new Smile().i);
		lable.setBounds(30, 300, 70, 70);
		add(lable);
	}
	
	//动图演示操作
	@Override
	public void paint(Graphics g) {
		super.paint(g);
		int y = 100;
		int x = 100;
		Toolkit tool = this.getToolkit();
		image = tool.getImage("img/food.jpg");
		imageP = tool.getImage("img/laoban.png");
		imageC = tool.getImage("img/maidou.png");
		if (Storage.Pdoing == 1) {
			g.drawImage(imageP, x - 100 + 60, y, imageP.getWidth(this), imageP.getHeight(this), this);
		} else {
			g.drawImage(imageP, x - 100, y, imageP.getWidth(this), imageP.getHeight(this), this);
		}
		if (Storage.Cdoing == 1) {
			g.drawImage(imageC, x + 150 - 60, y, imageC.getWidth(this), imageC.getHeight(this), this);
		} else {
			g.drawImage(imageC, x + 150, y, imageC.getWidth(this), imageC.getHeight(this), this);
		}
		for (int i = 0; i < Storage.Scount; i++) {
			g.drawImage(image, x + 40, y, image.getWidth(this), image.getHeight(this), this);
			y = y + image.getHeight(this);
		}
	}
3. JPanel Control

这个JPanel主要实现对界面的控制,也是这个项目的核心代码所在,创建对几个按钮和复选框的监听事件,实现按钮的动作,这里监听事件的原理和其他图形化编程的原理大同小异。使用getSelectedItem()函数实现对控件参数的返回,从而为后面处罚的事件提供参数。

class Control extends JPanel implements ActionListener {// control是控制选择的部分
	Font f = new Font("楷体", Font.PLAIN, 16);
	JLabel label1 = new JLabel("生产者个数:");
	JComboBox cmb1 = new JComboBox();
	JLabel label2 = new JLabel("消费者个数:");
	JComboBox cmb2 = new JComboBox();
	JLabel label3 = new JLabel("Buffer容量:");
	JComboBox cmb3 = new JComboBox();
	JLabel label4 = new JLabel("所做操作次数:");
	JComboBox cmb4 = new JComboBox();
	JButton start = new JButton("开始");
	JButton clear = new JButton("清空");
	JButton stopButton = new JButton("停止");
	Content content;
	// JLabel image=new JLabel();
	// Icon icon=new ImageIcon("img/food.jpg");

	public Control(Content content) {
		this.content = content;
		cmb1.addItem(1);
		cmb1.addItem(2);
		cmb1.addItem(3);
		cmb1.addItem(4);
		cmb2.addItem(1);
		cmb2.addItem(2);
		cmb2.addItem(3);
		cmb2.addItem(4);
		cmb3.addItem(1);
		cmb3.addItem(2);
		cmb3.addItem(3);
		cmb3.addItem(4);
		cmb4.addItem('1');
		cmb4.addItem('N');
		label1.setFont(f);
		label2.setFont(f);
		label3.setFont(f);
		label4.setFont(f);
		cmb1.setFont(f);
		cmb2.setFont(f);
		cmb3.setFont(f);
		cmb4.setFont(f);
		start.setFont(f);
		clear.setFont(f);
		stopButton.setFont(f);
		add(label1);
		add(cmb1);
		add(label2);
		add(cmb2);
		add(label3);
		add(cmb3);
		add(label4);
		add(cmb4);
		add(start);
		add(clear);
		add(stopButton);
		// image.setIcon(icon);
		// image.setBounds(300, 300, icon.getIconWidth(), icon.getIconHeight());
		// add(image);
	}

	public void actionPerformed(ActionEvent e) {// 四个组件的监听事件
		int temp1 = (int) cmb1.getSelectedItem();
		int temp2 = (int) cmb2.getSelectedItem();
		int temp3 = (int) cmb3.getSelectedItem();
		char temp4 = (char) cmb4.getSelectedItem();
		if (e.getActionCommand() == "开始") {
			content.clear();
			if (temp4 == '1') {// 选择一个生产者一个消费者一次操作
				PC.stop = 1;
				go1();
			} else if (temp1 == 1 && temp2 == 1 && temp3 == 1 && temp4 == 'N') {// 选择一个生产者一个消费者多次操作
				PC.stop = 0;
				go2();
			} else if (temp1 == 1 && temp2 == 1 && temp3 != 1 && temp4 == 'N') {// 选择一个生产者一个消费者多次操作且Buffer为n
				PC.stop = 0;
				go3(temp3);
			} else if (temp1 != 1 && temp2 != 1 && temp3 != 1 && temp4 == 'N') {// 选择多个生产者多个消费者多次操作且Buffer为n
				PC.stop = 0;
				go4(temp3,temp1,temp2);
			}
		} else if (e.getActionCommand() == "清空") {
			content.clear();
		} else if (e.getActionCommand() == "停止") {
			PC.stop = 1;
		} else {
			// System.out.println("Input Error!");
		}
	}

	public void go1() {// 一个生产者一个消费者的运行程序
		Storage storage1 = new Storage();
		Thread p1 = new Thread(new Producer(storage1));
		Thread c1 = new Thread(new Consumer(storage1));
		p1.start();
		c1.start();
		/*
		 * System.out.println("【生产者Thread-1】生产一个产品,现库存1");
		 * System.out.println("【消费者Thread-2】消费一个产品,现库存0");
		 */
	}

	}

}
4. 生产者消费者代码

生产者消费者问题(Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。生产者生成一定量的数据放到缓冲区中,然后重复此过程;与此同时,消费者也在缓冲区消耗这些数据。生产者和消费者之间必须保持同步,要保证生产者不会在缓冲区满时放入数据,消费者也不会在缓冲区空时消耗数据。不够完善的解决方法容易出现死锁的情况,此时进程都在等待唤醒。
生产者消费者问题的java代码网上有很多,大佬们讲解的都非常详细,我这里就不过多赘述了。我这里使用的是wait()和notify()的方法,看起来这段代码很复杂,其实核心的东西只有几样,感觉java都把其中的具体细节原理全部封装好了,只需要调用就好了。这里面synchronized()函数的作用非常重要,给代码块或业务逻辑快添加锁(给代码块或业务逻辑快添加锁),它可以将produce和consum过程代码块同步起来,共享一个Storage资源。consumer和producer中的run是核心方法,也是控制打印的根源。
在Storage中,我还设置了几个属于类的变量,从而实现后面动画演示中参数需要。

package pcModel;

public class Producer implements Runnable {
	private Storage storage;

	public Producer(Storage storage) {
		this.storage = storage;
	}

	@Override
	public void run() {
		/*
		 * if(PC.times==1) { storage.produce(); return; }else {
		 */
		do {
			storage.produce();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} while (PC.stop == 0);
		/* } */
	}
}

package pcModel;

public class Consumer implements Runnable {
	private Storage storage;

	public Consumer(Storage storage) {
		this.storage = storage;
	}

	@Override
	public void run() {
/*    	if(PC.times==1) {
    		storage.consume();
    		return;
    	}else {*/
		do {
			storage.consume();
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}while(PC.stop==0);
    	/*}*/
	}
}

package pcModel;

import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingQueue;

public class Storage {
	public static int Scount=0;
	public static int Pdoing=0;
	public static int Cdoing=0;
	public Storage(int count) {
		this.MAX_SIZE = count;
		this.Scount=0;
		this.Pdoing=0;
		this.Cdoing=0;
	}
	
	
	public Storage() {
		this.Scount=0;
		this.Pdoing=0;
		this.Cdoing=0;
	}

	public static int num3;

	// 仓库容量
    private int MAX_SIZE = 1;
    // 仓库存储的载体
    private LinkedList<Object> list = new LinkedList<>();

    public void produce() {
        synchronized (list) {	       	
            while (list.size()+1> MAX_SIZE) {
            	
                System.out.println("【生产者" + Thread.currentThread().getName()
		                + "】仓库已满");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           
            list.add(new Object());
            System.out.println("【生产者" + Thread.currentThread().getName()
                    + "】生产一个产品,现库存" + list.size());
           try {
                Scount++;
                Pdoing=1;
                Cdoing=0;
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
            list.notifyAll();
       
    	}
    }

    public void consume() {
        synchronized (list) {
            while (list.size() == 0) {
                System.out.println("【消费者" + Thread.currentThread().getName() 
						+ "】仓库为空");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.remove();
            System.out.println("【消费者" + Thread.currentThread().getName()
                    + "】消费一个产品,现库存" + list.size());
           try {
            	Pdoing=0;
                Cdoing=1;
                Scount--;
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

            list.notifyAll();
        }
    }
}

5. 输出重定向

这里是输出重定向的类,具体原理查看参考文献,感觉很复杂,我就直接拿过来用了。

class JTextAreaOutputStream extends OutputStream {// 将控制台内容重定向到文本框中
	private final JTextArea destination;

	public JTextAreaOutputStream(JTextArea destination) {
		if (destination == null)
			throw new IllegalArgumentException("Destination is null");

		this.destination = destination;
	}

	@Override
	public void write(byte[] buffer, int offset, int length) throws IOException {
		final String text = new String(buffer, offset, length);
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				destination.append(text);
			}
		});
	}

	@Override
	public void write(int b) throws IOException {
		write(new byte[] { (byte) b }, 0, 1);
	}
}

四、心得体会

在这次项目的开发过程中,遇到了很多的困难。首先就是java和java swing编程的不同之处,需要将控制台内容显示再图形化界面上,经过多次摸索后,发现了将控制台输出内容重定向到控件上的方法;其次就是进程开始后无法停止,开启另一个线程后原先的线程还会继续执行,苦思冥想后无果,偶然和同学的讨论中发现可以声明全局变量来控制线程的停止,最终实现了停止功能;顺着全局变量的思想,考虑到可以用变量控制图片位置,进而实现动画模拟的效果,通过上网搜集相关函数,最终实现相关功能。

五、参考文献

[1] Java多种方式解决生产者消费者问题(十分详细)https://blog.csdn.net/ldx19980108/article/details/81707751
[2] 图片的动态移动
https://www.cnblogs.com/fanfan-12511/p/6861223.html
[3] swing重定向输出到jtextArea
https://blog.csdn.net/A694543965/article/details/72083035
[4] https://download.csdn.net/download/grace_qiao/2589721

小白不易,大佬轻喷,欢迎大家批评指正。

你可能感兴趣的:(操作系统)