zoukankan      html  css  js  c++  java
  • java多线程15 :wait()和notify() 的生产者/消费者模式

    什么是生产者/消费者模型

    一种重要的模型,基于等待/通知机制。生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点:

    1、生产者生产的时候消费者不能消费

    2、消费者消费的时候生产者不能生产

    3、缓冲区空时消费者不能消费

    4、缓冲区满时生产者不能生产

    生产者/模型作为一种重要的模型,它的优点在于:

    1、解耦。因为多了一个缓冲区,所以生产者和消费者并不直接相互调用,这一点很容易想到,这样生产者和消费者的代码发生变化,都不会对对方产生影响,这样其实就把生产者和消费者之间的强耦合解开,变为了生产者和缓冲区/消费者和缓冲区之间的弱耦合

    2、通过平衡生产者和消费者的处理能力来提高整体处理数据的速度,这是生产者/消费者模型最重要的一个优点。如果消费者直接从生产者这里拿数据,如果生产者生产的速度很慢,但消费者消费的速度很快,那消费者就得占用CPU的时间片白白等在那边。有了生产者/消费者模型,生产者和消费者就是两个独立的并发体,生产者把生产出来的数据往缓冲区一丢就好了,不必管消费者;消费者也是,从缓冲区去拿数据就好了,也不必管生产者,缓冲区满了就不生产,缓冲区空了就不消费,使生产者/消费者的处理能力达到一个动态的平衡


    本篇先将利用wait()/notify()实现生产者/消费者的几点注意,最后讲解通关管道字节,字符流等也可以实现线程通信

    利用wait()/notify()实现生产者/消费者模型

    既然生产者/消费者模型有一个缓冲区,那么我们就自己做一个缓冲区,生产者和消费者的通信都是通过这个缓冲区的。value为""表示缓冲区空,value不为""表示缓冲区满:

    public class ValueObject
    {
        public static String value = "";
    }

    接下来就是一个生产者了,如果缓冲区满了的,那么就wait(),不再生产了,等待消费者消费完通知;如果缓冲区是空的,那么就生产数据到缓冲区中

    public class Producer
    {
        private Object lock;
        
        public Producer(Object lock)
        {
            this.lock = lock;
        }
        
        public void setValue()
        {
            try
            {
                synchronized (lock)
                {
                    if (!ValueObject.value.equals(""))
                        lock.wait();
                    String value = System.currentTimeMillis() + "_" + System.nanoTime();
                    System.out.println("Set的值是:" + value);
                    ValueObject.value = value;
                    lock.notify();
                }
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    消费者类似,如果缓冲区是空的,那么就不再消费,wait()等待,等待生产者生产完通知;如果缓冲区不是空的,那么就去拿数据:

    public class Customer
    {
        private Object lock;
        
        public Customer(Object lock)
        {
            this.lock = lock;
        }
        
        public void getValue()
        {
            try
            {
                synchronized (lock)
                {
                    if (ValueObject.value.equals(""))
                        lock.wait();
                    System.out.println("Get的值是:" + ValueObject.value);
                    ValueObject.value = "";
                    lock.notify();
                }
            } 
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    写个主函数,开两个线程调用Producer里面的getValue()方法和Customer()里面的setValue()方法:

    public static void main(String[] args)
    {
        Object lock = new Object();
        final Producer producer = new Producer(lock);
        final Customer customer = new Customer(lock);
        Runnable producerRunnable = new Runnable()
        {
            public void run()
            {
                while (true)
                {
                    producer.setValue();
                }
            }
        };
        Runnable customerRunnable = new Runnable()
        {
            public void run()
            {
                while (true)
                {
                    customer.getValue();
                }
            }
        };
        Thread producerThread = new Thread(producerRunnable);
        Thread CustomerThread = new Thread(customerRunnable);
        producerThread.start();
        CustomerThread.start();
    }

    看一下运行结果:

    ...
    Set的值是:1444025677743_162366875965845
    Get的值是:1444025677743_162366875965845
    Set的值是:1444025677743_162366875983541
    Get的值是:1444025677743_162366875983541
    Set的值是:1444025677743_162366876004776
    Get的值是:1444025677743_162366876004776
    ...

    生产数据和消费数据一定是成对出现的,生产一个消费一个,满了不生产,空了不消费,生产者不能无限生产,消费者也不能无限消费,符合生产者/消费者模型。生产者速度快,就不占用CPU时间片,等着消费者消费完通知它继续生产,这块时间片可以用来给其他线程用。

     上面是一个很普通的生产者/消费者模型,且只有一个生产者和消费者,执行正常,如果有多个的话就会产生假死情况。注意上面的if判断是有问题,在一生产/多消费的线程执行的话,会出现程序混乱的问题,因为没有二次验证,改为while就可以,这里可以参考java多线程14 :wait()和notify()/notifyAll() 这里有些该情况



    生产者/消费者操作值---假死






    上面模拟了多个生产者/消费者 操作值产生的假死情况,在这里为什么会出现假死的情况,就是因为多个生产者/消费者通时执行,而一次执行只notify唤醒了一个wait的线程,由于多线程唤醒的执行顺序是无序的,很有可能生产者notify唤醒了一个生产者,这样冲突互相wait,就造成了线程假死。
    解决上面假死的情况就是使用notifyAll,这样就通知了所有wait的线程,包括生产/消费,只要有一个不是同类的被唤醒就不会出现wait假死情况了


    一生产者/多消费者--操作栈
    一生产者/多消费者如果使用if判断的会导致程序错乱,抛出异常,验证



    上面是一生产 一消费的情况,正常打印。下面修改代码,变成一生产/多消费



    一生产者/多消费者--解决if wait条件为while,并解决假死notify改为notifyAll



    生产/消费模式下分支用whlie二次判断,notifyAll解决假死



    通过管道进行线程间通信---字节流




    从上面来看,管道字符流也是可以实现线程通信的




    通过管道进行线程间通信---字符流


    run





    跟字节流一样

  • 相关阅读:
    2020年. NET Core面试题
    java Context namespace element 'component-scan' and its parser class ComponentScanBeanDefinitionParser are only available on JDK 1.5 and higher 解决方法
    vue 淡入淡出组件
    java http的get、post、post json参数的方法
    vue 父子组件通讯案例
    Vue 生产环境解决跨域问题
    npm run ERR! code ELIFECYCLE
    Android Studio 生成apk 出现 :error_prone_annotations.jar (com.google.errorprone:error) 错误
    记忆解析者芜青【总集】
    LwIP应用开发笔记之十:LwIP带操作系统基本移植
  • 原文地址:https://www.cnblogs.com/signheart/p/624cac32b55225a1e623922a4bb5b47f.html
Copyright © 2011-2022 走看看