生产者和消费者模型

时间:2020-12-07 17:37:41

生产者和消费者模型

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

模型:

生产者和消费者模型

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

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

这就相当于去包子店吃包子,你要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中的典型代码
生产者和消费者模型