• 整理整理生产者消费者模式,用通俗的话描写叙述


    之前学习多线程问题遇到的最大的难度就是,非常多。生产者消费者模式是比較经典的多线程问题,看似 不难。但实际上有非常多地方值得注意的。

    首先是几个问题


    问题1 一共同拥有哪些对象?

    生产者与消费者是肯定有的,生产者与消费者之间另一个缓冲区对象。用以保存生产与消费的目标,另一个对象就是主线程对象,用来执行多个线程的。

            追问:为什么要有一个缓冲区对象?

            答:为了实现生产者与消费者解耦,互补依赖或者关联。

            追问:每一个对象都包括哪些变量与方法?

            答:生产者包括缓冲区对象并在run()方法里面运行生产调度命令;消费者含缓冲区对象并在run()方法里面运行消费调度命令;缓冲区则保存产品的数组/Stack/Queue以及生产与消费的详细指令。主线程对象则用来实现生产者与消费者线程创建与运行的。


    问题2 哪些方法须要同步?

    缓冲区对象的生产与消费必须实现同步。


    我在进行研究的时候,会出现例如以下错误的代码:

    /**
     *  项目名称:
     *  文件说明:
     *  主要特点:生产者消费者模式
     *  版本:1.0
     *  制作人:lcx
     *  创建时间:2013-12-3
     **/
    package treadgame;
    
    import java.util.Stack;
    
    /**
     * @author lcx
     *
     */
    public class Producer_Consumer1 {
    
    	public static void main(String[] args) {
    		Stack<String> apples=new  Stack<String>();
    		Thread td1=new Thread(new Producer1("生产者1",apples));
    		Thread td2=new Thread(new Consumer1("消费者",apples));
    		td2.start();
    		td1.start();
    	}
    }
    
    class Producer1 implements Runnable
    {
    	String name;
    	Stack<String> apples; 
    	public Producer1(String name,Stack<String> apples)
    	{
    		this.name=name;
    		this.apples=apples;
    	}
    
    	public synchronized void produce(String apple) throws Exception
    	{
    		//				System.out.println("生产者 此时一共"+apples.size()+"个APPLE");
    		if(apples.size()>=5)
    		{
    			System.out.println(name+"堵塞");
    			wait();
    		}
    		notify();
    		apples.push(apple);
    		System.out.println(name+"已经生产了"+apple);
    	}
    
    	public void run() {
    		for(int i=0;i<10;i++)
    		{
    			try {
    				produce("苹果"+i);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    class Consumer1 implements Runnable
    {
    	String name;
    	Stack<String> apples; 
    	public Consumer1(String name,Stack<String> apples)
    	{
    		this.name=name;
    		this.apples=apples;
    	}
    
    	public synchronized void consume() throws Exception
    	{
    		//		System.out.println("消费者 此时一共"+apples.size()+"个APPLE");
    		if(apples.size()==0)
    		{
    			System.out.println(name+"堵塞");
    			wait();
    		}
    		notify();
    		String apple=apples.pop();
    		System.out.println(name+"已经消费了"+apple);
    	}
    
    	public void run() {
    		for(int i=0;i<10;i++)
    		{
    			try {
    				consume();
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    }
    

    然后一直会出现死锁。细致思考后发现wait与notify出现的位置不对。wait的作用是运行wait的线程堵塞。然后释放对象锁;notify是唤醒改对象的其它线程。

    所以wait方法与notify应该是相应的一个对象,而我在代码中却希望通过在Consumer中的wait唤醒Producer的对象。肯定是不对的。

    结果改动后,例如以下

    /**
     *  项目名称:
     *  文件说明:
     *  主要特点:生产者消费者模式
     *  版本:1.0
     *  制作人:lcx
     *  创建时间:2013-12-3
     **/
    package treadgame;
    
    import java.util.Stack;
    
    /**
     * @author lcx
     *
     */
    public class Producer_Consumer2 {
    
    	public static void main(String[] args) {
    		Resturant res=new Resturant();
    		Thread td1=new Thread(new Producer2(res));
    //		Thread td2=new Thread(new Producer("生产者2",apples));
    //		Thread td3=new Thread(new Producer("生产者3",apples));
    		Thread td4=new Thread(new Consumer2(res));
    		td4.start();
    		td1.start();
    		//		td2.start();
    		//		td3.start();
    	}
    }
    
    class Resturant
    {
    	Stack<String> apples=new  Stack<String>();
    	
    	public synchronized void produce(String apple) throws Exception
    	{
    		//				System.out.println("生产者 此时一共"+apples.size()+"个APPLE");
    		if(apples.size()>=5)
    		{
    			System.out.println("生产堵塞");
    			wait();
    		}
    		notify();
    		apples.push(apple);
    		System.out.println("已经生产了"+apple);
    	}
    
    	public synchronized void consume() throws Exception
    	{
    		//		System.out.println("消费者 此时一共"+apples.size()+"个APPLE");
    		if(apples.size()==0)
    		{
    			System.out.println("消费堵塞");
    			wait();
    		}
    		notify();
    		String apple=apples.pop();
    		System.out.println("已经消费了"+apple);
    	}
    	
    }
    
    class Producer2 implements Runnable
    {
    	Resturant res;
    	public Producer2(Resturant res)
    	{
    		this.res=res;
    	}
    
    	public void run() {
    		for(int i=1;i<=10;i++)
    		{
    			try {
    				res.produce("苹果"+i);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    class Consumer2 implements Runnable
    {
    	Resturant res;
    	Stack<String> apples; 
    	public Consumer2(	Resturant res)
    	{
    		this.res=res;
    	}
    
    
    	public void run() {
    		for(int i=1;i<=10;i++)
    		{
    			try {
    				res.consume();
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    }
    

    这样就能够了。


    问题3 若produce与consume方法都不是同步方法,会出现什么问题?

    会出现IllegalMonitorStateException异常。由于wait必须在同步代码块中。



    总结:

    就我如今的经验看来,分析多线程问题。应该分析的是资源,而不是线程。由于线程的运行是由他们与资源的关系所决定的。


            

  • 相关阅读:
    golng切片实现分页
    go mgo包 简单封装 mongodb 数据库驱动
    docker 制作自己的镜像
    mongodb 数据库操作——备份 还原 导出 导入
    Override
    Parallel.For循环与普通的for循环的比较
    C#死锁案例代码
    C#的构造函数在基类和父类中执行顺序
    C#构造函数
    C# 多线程的死锁
  • 原文地址:https://www.cnblogs.com/clnchanpin/p/6802033.html
Copyright © 2020-2023  润新知