生产者和消费者模型

生产者和消费者模型

线程通信:不同的线程执行不同的任务,如果这些任务有某种关系,各个线程必须要能够通信,从而完成工作。线程通信中的经典问题:生产者和消费者问题

模型:

这个模型也体现了面向对象的设计理念:低耦合

也就是为什么生产者生产的东西为什么不直接给消费者,还有经过一个缓冲区(共享资源区)

这就相当于去包子店吃包子,你要5个包子,老板把5个人包子放在一个盘子中再给你,这个盘子就是一个缓冲区。

现在模拟一个生产者和消费者模型

  • 生产者生产“李小龙 男”,消费者消费也就是打印出“李小龙 男”

  • 生产者生产“狗晗 女”,消费者消费也就是打印出“狗晗 女”

  • 要求生产者生产一个,消费者消费一个。也就是缓冲区为1

  • 期望结果:

    李小龙 男

    狗晗 女

    李小龙 男

    狗晗 女

    ···

解决问题

需要创建4个类,分别是生产者,消费者,共享资源类,测试类

  • 生产者类实现Runnable接口,覆盖run方法

    public class Producer implements Runnable{
    	ShareResource resource = null;
    	public Producer(ShareResource resource){
    		this.resource = resource;
    	}
    	@Override
    	public void run() {
    		for(int i = 0; i < 50;i++){
    			if(i % 2 == 0){
    				resource.product("李小龙", "男");
    			}else{
    				resource.product("狗晗","女");
    			}
    		}
    	}
    	
    }
    
  • 消费者类实现Runnable接口,覆盖run方法

    public class Consumer implements Runnable {
    	ShareResource resource = null;
    	public Consumer(ShareResource resource){
    		this.resource = resource;
    	}
    	public void run(){
    		for(int i = 0;i < 50;i++){
    			resource.consume();
    		}
    	}
    }
    
  • 共享资源类有生产者和消费者两个类的引用

    public class ShareResource {
    	private String name;
    	private String sex;
    	
    	public void product(String name,String sex){
    	    this.name = name;
            try{
                Thread.sleep(10);
            }catch(Exception e){
                e.printStackTrace();
            }
    		this.sex = sex;
    	}
    	
    	public void consume(){
            try{
                Thread.sleep(10);
            }catch(Exception e){
                e.printStackTrace();
            }
    		System.out.println(this.name + " " + this.sex);
    	}
    }
    
  • 测试类可以启动线程

    public class Demo {
    	public static void main(String[]args){
    		ShareResource resource = new ShareResource();
    		new Thread(new Producer(resource)).start();
    		new Thread(new Consumer(resource)).start();
    	}
    }
    

运行后发现,结果性别发生了紊乱:

我靠,这我龙哥愿意吗,怎么回事?

就是因为多线程并发同一资源,要把它们的方法用synchronized修饰保证它们生产和消费不同时进行,当你加上synchronized之后:

确实性别紊乱的问题是解决了,狗晗是高兴了,但是它们并不是交替出现的啊,怎么回事?

你要交替出现,必须一个等着一个啊,你生产完了,你得停下来休息啊,让消费者先消费,等到消费者消费完之后,让消费者把你叫醒再继续生产,消费者就又去休息去了,这个消费者优点类似于吃了睡,睡了在吃一样。

wait和notify方法

  • wait方法:执行该方法的对象释放同步锁,JVM把该线程存放到等待池中,等待其他线程唤醒

  • notify方法:唤醒在等待池中的等待的任意一个线程,把线程转移到锁池

  • notifyAll方法:唤醒在等待池中等待的所有线程,把线程转移到锁池

    等待池:没有机会获取同步锁,只能等待被唤醒,被转移到锁池中。

    锁池: 当前生产者在生产数据的时候(先拥有同步锁),其他线程就在锁池中等待获取锁.,当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢锁的使用权.

又来看这个模型,应该得定义一个判断当前共享资源区是否为空,如果为空,消费者就应该等待生产者生产,当生产者生产 完毕后应该唤醒消费者进行消费。如果不为空,生产者就应该等待消费者,等消费者完毕后再唤醒生产者继续生产

public class ShareResource {
	private String name;
	private String sex;
	private boolean isEmpty = true;//判断共享区空和满的一个标志
	//生产者生产
	synchronized public void product(String name,String sex){
		try {
			while(!isEmpty){//共享区不空,生产者需要停下等待消费者消费
				/**
				 * this是同步锁(同步监听对象),wait方法是Object类中 的方法,调用该方法就释放同步锁
				 * 然后JVM把该线程存放到等待池中,等待其他线程唤醒该线程
				 * 该方法只能被同步监听对象调用,否则报错IllegalMonitorStateException.
				 */
				
				this.wait();
			}
			//----------开始生产-----------
			this.name = name;
			Thread.sleep(10);
			this.sex = sex;
			//---------结束生产------------
			isEmpty = false;  //设置共享区为不空
			/**
			 * notify方法是唤醒等待池中的随机一个线程,把线程转移到锁池中等待(锁池中有机会获取到锁)
			 * notifyAll方法是唤醒等待池中的所有线程,把线程转移到锁池中等待
			 * 该方法只能被同步监听对象锁调用,否则报错
			 */
			this.notify();	//唤醒一个消费者               notify是唤醒其中一个,notifyAll是唤醒全部
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	//消费者消费
	synchronized public void consume(){
		try {
			while(isEmpty){//当共享区为空时,消费者进行等待
				this.wait();
			}
			//------------开始消费-------------
			Thread.sleep(30);
			System.out.println(name + " " + sex);
			//------------结束消费-------------
			isEmpty = true;
			this.notify();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	} 
}

用synchronized修饰方法,会自动获取锁,自动释放锁。从java5开始使用Lock机制取代synchronized代码块和synchronized方法,使用Condition接口对象的await,signal,signalAll方法取代notify,notifyAll,用法还是大同小异

public class ShareResource {
	private String name;
	private String sex;
	private boolean isEmpty = true;
	private final Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	public void product(String name,String sex){
		lock.lock();//获取锁
		try {
			while(!isEmpty){//不空,生产者要等待消费者消费
				condition.await();//等待,相当于this.wait();
			}
			this.name = name;
			Thread.sleep(10);
			this.sex = sex;
			
			isEmpty = false;//把共享区置为不空
			condition.signal();//唤醒一个线程,相当于this.notify().  signalAll 可以唤醒全部线程
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();//释放锁
		}
	}
	
	public void consume(){
		lock.lock();
		try {
			while(isEmpty){//共享区为空,需要等待生产者生产
				condition.await();
			}
			
			Thread.sleep(20);
			System.out.println(this.name + " " + this.sex);
			
			isEmpty = false;
			condition.signal();
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
}

结果就是:

Lock机制

创建一个Lock接口实现类ReetranLock的对象,进入同步方法后立即加锁,lock.lock();最后释放锁,看API中的典型代码

posted @ 2018-10-27 21:18  earth腾飞  阅读(4119)  评论(0编辑  收藏  举报