Java可视化实现生产者消费者问题

引言:生产者消费者问题是一个十分经典的多线程问题。为了更加形象地描述这个问题,采用可视化的形式展示此过程。

在这里插入图片描述
在这里插入图片描述

1、问题重述

     生产者消费者问题也称有限缓冲问题。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

    当我们在思考如何解决这个问题的时候一定要联系实际。实际生活中(不考虑直接点对点模式),工厂生产的商品一般会通过经销商销售,消费者从经销商中购买商品。经销商手中的存货是有限的,同时他需要的商品也是有限的。当他不需要商品的时候,生产商不能再生产更多商品;当他没有存货的时候,消费者无法从他手中购买。这里,经销商就相当于一个缓冲区。

    这种生产者-消费者模式有什么好处呢?使用这种模式,可以让消费者、生产者互相独立,生产者不需要依赖消费者的消费速度,它只关心缓冲区的状况。

2、前期准备

实现方法有四种:
  • wait()和notify()方法
  • await()和signal()方法
  • 阻塞队列方法
  • 管道方法
  • 此教程采用第一种方法。
    2.1 synchronized

        多线程并发存在着线程安全问题,主要在于存在共享数据以及多个线程共同操作共享数据。使用synchronized可以保证线程互斥的访问代码。其原理在于它可以保证方法或者代码块在同一时刻只有一个可以进入到临界区,还可以保证共享变量的内存可见性

        synchronized一般称"同步锁",在修饰代码块的时候需要传入一个对象作为"锁"的对象。线程同步就是利用锁机制先给共享资源上锁,只有拿到锁的线程才可以访问共享资源,其他线程进入等待状态

    2.2 wait()方法

        wait方法()是Object类的方法,其作用是使当前执行代码的线程进入等待。在调用wait()方法之前,线程必须获得该对象的锁,即只能在同步方法或同步块中调用wait()方法。wait()方法执行之后,当前线程释放锁。从wait()返回之前,线程会与其他线程产生资源竞争。当调用wait()时,如果没有锁将会抛出异常

    2.3 notify()方法与notifyAll()方法

        notify()方法用来通知那些可能等待该对象的对象锁的其他线程,若有多个线程,则随机选择wait状态的线程对其notify,使它获得该对象的对象锁。但是,线程不会马上释放锁,而是等到执行notify()方法的线程将程序执行完后即退出synchronized语句块后,该线程才会释放锁。注意,操作不当可能会出现过早通知

        notifyAll()使所有线程退出wait状态

        总之,简单来讲:wait()使线程停止运行,notify()使线程继续运行

    3、实现消费者-生产者模型

    3.1 界面设计
    创建Frame类和UI类,Frame创建窗体,UI添加控件以及绑定事件。该部分只介绍布局,绑定事件后面再细讲。用进度条代表生产/消费进度。界面如下图所示:

    在这里插入图片描述

    class Frame extends JFrame
    {
    	public int width=1500;
    	public int height=1500;
    	UI ui=new UI();
    	public Container container;
    	public Frame()
    	{
    		setTitle("生产者消费者问题");
    		setSize(width,height);
    		setLocation(300,0);		
    		container=getContentPane();
    		container.add(ui);
    	}
    }
    
    class UI extends JPanel
    {
    	public static JProgressBar producerBar;//表示生产者生产进度
    	public static JLabel producerJLabel;//表示生产者生产个数
    	public static JLabel bufferJLabel;//缓冲区
    	public static JLabel amountJLabel;//缓冲区个数
    	public static JProgressBar consumerBar;//表示消费者消费速度
    	public static JLabel consumerJLabel;//表示消费者消费个数
    	JButton bt1;//生产者开始生产
    	JButton bt2;//生产者停止生产
    	JButton bt3;//消费者开始消费
    	JButton bt4;//消费者停止消费
    	public UI()
    	{
    		setLayout(null);
    		setSize(1400,1400);
    		producerBar=new JProgressBar();
    		consumerBar=new JProgressBar();
    		bt1=new JButton("开始生产");
    		bt2=new JButton("停止生产");
    		bt3=new JButton("开始消费");
    		bt4=new JButton("停止消费");
    		producerJLabel=new JLabel("生产者");
    		consumerJLabel=new JLabel("消费者");
    		bufferJLabel=new JLabel("缓冲区 (最大容量25)");
    		amountJLabel=new JLabel("商品数量:0");
    		producerBar.setBackground(Color.WHITE);
    		producerBar.setForeground(Color.BLACK);
    		consumerBar.setBackground(Color.WHITE);
    		producerBar.setForeground(Color.BLACK);
    		bt1.setBounds(50,170,70,60);
    		bt2.setBounds(150,170,70,60);
    		bt3.setBounds(950, 700, 70, 60);
    		bt4.setBounds(1050,700,70,60);
    		bufferJLabel.setBounds(550,350,200,200);
    		amountJLabel.setBounds(550,300,200,400);
    		producerJLabel.setBounds(50,70,50,50);
    		producerBar.setBounds(50, 120, 480, 40);
    		consumerJLabel.setBounds(950,600,50,50);
    		consumerBar.setBounds(950,650,480,40);
    		add(producerBar);
    		add(producerJLabel);
    		add(consumerBar);
    		add(consumerJLabel);
    		add(bt1);
    		add(bt2);
    		add(bt3);
    		add(bt4);
    		add(bufferJLabel);
    		add(amountJLabel);
    	}
    }
    
    3.2 模型设计
    根据问题描述即可知道设计思路,创建三个类分别是:Buffer类(缓冲区)、Producer类(生产者)、Consumer类(消费者)。在Buffer类里面我们要模仿生产者生产商品以及消费者消费商品,这里就产生了共享资源——商品。因此,我们需要用到synchronized锁保证线程安全,采用wait()/notify()方法。注意到一点,在消费者-生产者模式下,生产者在缓冲区未满的情况下只管不断生产,消费者在缓冲区未空的情况下只管不断消费。
    3.2.2 Buffer类
    在Buffer类,我们需要实现的是模拟商品运到仓库和商品从仓库出去以及相应的UI。UI方面,需要实时显示缓冲区商品数量。
    class Buffer
    {
    	private static final int max=25;//缓冲区最大容量
    	private LinkedList<Object>list=new LinkedList<Object>();//表示商品实体
    	JLabel amount;//缓冲区当前商品数量		
    	public Buffer(JLabel amount)
    	{
    		this.amount=amount;
    	}
    	public synchronized void produce()//
    	{
    		while(list.size()==max)//当缓冲区达到最大容量时,如果不释放资源,则生产进程一直处于阻塞状态
    		{
    			amount.setText("缓冲区已满,生产阻塞");
    			try {
    				wait();//生产阻塞
    			} catch (Exception e) {
    				// TODO: handle exception
    				e.printStackTrace();
    			}
    		}
    		//没有达到缓冲区最大容量
    		list.add(new Object());//生产商品		
    		amount.setText("商品数量: "+list.size());
    		notifyAll();//当生产一个商品之后,可以唤醒其他线程
    	}
    	public synchronized void consume()
    	{
    		while(list.size()==0)//当缓冲区为空时
    		{
    			amount.setText("缓冲区已空,消费阻塞");
    			try {
    				wait();
    			} catch (Exception e) {
    				// TODO: handle exception
    				e.printStackTrace();
    			}
    		}
    		list.remove();//消费商品
    		amount.setText("商品数量: "+list.size());
    		notifyAll();
    	}
    }
    
    3.2.3 Producer类
    用进度条模拟消费者不断生产的过程
    class Producer implements Runnable
    {
    	Buffer buffer;
    	JProgressBar produceBar;//生产进度条
    	int i;
    	public Producer(Buffer buffer,JProgressBar produceBar) {
    		// TODO Auto-generated constructor stub
    		this.buffer=buffer;
    		this.produceBar=produceBar;
    	}
    	@Override
    	public void run()
    	{
    		while(true)
    		{
    			try {
    				if(i<=25)
    				{
    					i++;
    					produceBar.setValue(i*4);
    					try {
    						Thread.sleep(200);
    					} catch (Exception e) {
    						// TODO: handle exception
    						e.printStackTrace();
    					}
    				}
    				buffer.produce();
    			} catch (Exception e) {
    				// TODO: handle exception
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    3.2.4 Consumer类
    用进度条模拟消费者不断消费的过程
    class Consumer implements Runnable
    {
    	Buffer buffer;	
    	JProgressBar consumeBar;//消费进度条
    	int i;
    	public Consumer(Buffer buffer,JProgressBar consumeBar) {
    		// TODO Auto-generated constructor stub
    		this.buffer=buffer;
    		this.consumeBar=consumeBar;
    	}
    	@Override
    	public void run()
    	{
    		while(true)
    		{
    			try {				
    				if(i<=25)
    				{
    					i++;
    					consumeBar.setValue(i*4);
    					try {
    						Thread.sleep(200);
    					} catch (Exception e) {
    						// TODO: handle exception
    						e.printStackTrace();
    					}
    				}
    				buffer.consume();
    			} catch (Exception e) {
    				// TODO: handle exception
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    3.2.5 事件绑定
    主要是通过按钮来控制生产/消费的开始与暂停。在UI类的构造函数里面实例化对象,并为按钮绑定监听器。同时申请两个线程,分别控制生产行为和消费行为。
    	Buffer buffer;//缓冲区
    	Producer producer;//生产者
    	Consumer consumer;//消费者
    	Thread thread1;//线程1用于控制生产行为的开始与暂停
    	Thread thread2;//线程2用于控制消费行为的开始与暂停
    	buffer=new Buffer(amountJLabel);
    	public UI()
    	{
    		bt1.addActionListener(new ActionListener() {//开始生产		
    			@Override
    			public void actionPerformed(ActionEvent e) {
    				// TODO Auto-generated method stub
    				if(thread1!=null)
    				{
    					try {
    						thread1.stop();
    					} catch (Exception e2) {
    						// TODO: handle exception
    					}
    				}
    				thread1=new Thread(new Producer(buffer,producerBar));
    				thread1.start();
    			}
    		});
    		bt2.addActionListener(new ActionListener() {		
    			@Override
    			public void actionPerformed(ActionEvent e) {
    				// TODO Auto-generated method stub
    				if(thread1!=null)
    				{
    					try {
    						thread1.stop();
    					} catch (Exception e2) {
    						// TODO: handle exception
    						e2.printStackTrace();
    					}
    				}
    			}
    		});
    		bt3.addActionListener(new ActionListener() {			
    			@Override
    			public void actionPerformed(ActionEvent e) {
    				// TODO Auto-generated method stub
    				if(thread2!=null)
    				{
    					try {
    						thread2.stop();
    					} catch (Exception e2) {
    						// TODO: handle exception
    						e2.printStackTrace();
    					}
    				}
    				thread2=new Thread(new Consumer(buffer,consumerBar));
    				thread2.start();
    			}
    		});
    		bt4.addActionListener(new ActionListener() {			
    			@Override
    			public void actionPerformed(ActionEvent e) {
    				// TODO Auto-generated method stub
    				if(thread2!=null)
    				{
    					try {
    						thread2.stop();
    					} catch (Exception e2) {
    						// TODO: handle exception
    						e2.printStackTrace();
    					}
    				}
    			}
    		});
    	}
    
  • 11
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值