zoukankan      html  css  js  c++  java
  • java 生产者消费者问题 并发问题的解决

    引言

      生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间内共用同一个存储空间,如下图所示,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况:

    生产者消费者图

      存储空间已满,而生产者占用着它,消费者等着生产者让出空间从而去除产品,生产者等着消费者消费产品,从而向空间中添加产品。互相等待,从而发生死锁

      生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。解决生产者/消费者问题的方法可分为两类:

      (1)采用某种机制保护生产者和消费者之间的同步;

      (2)在生产者和消费者之间建立一个管道。

      第一种方式有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。第二种管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。因此本文只介绍同步机制实现的生产者/消费者问题。

      同步问题核心在于:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。Java语言在多线程编程上实现了完全对象化,提供了对同步机制的良好支持。在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。

    (1)wait() / notify()方法

    (2)await() / signal()方法

    (3)BlockingQueue阻塞队列方法

    (4)PipedInputStream / PipedOutputStream

    本文只介绍最常用的前两种种,第三、四种暂不做讨论,有兴趣的读者可以自己去网上找答案。

    一、wait() / notify()方法

      wait() / nofity()方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。

      wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等等状态,让其他线程执行。

      notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。

    代码实现:

    1、仓库类

    import java.util.LinkedList;  
    
    /** 
     * 仓库类Storage实现缓冲区 
     *  
     * @author zcr 
     */  
    public class Storage  
    {  
        // 仓库最大存储量  
        private final int MAX_SIZE = 100;  
      
        // 仓库存储的载体  
        private LinkedList<Object> list = new LinkedList<Object>();  
      
        /**
         * 生产num个产品
         * @param num 生产产品的数量
         */
        public void produce(int num)  
        {  
            // 同步代码段  
            synchronized (list)  
            {  
                // 如果仓库剩余容量不足  
                while (list.size() + num > MAX_SIZE)  
                {  
                    System.out.println("【要生产的产品数量】:" + num + " 	 【库存量】:"  
                            + list.size() + "	 暂时不能执行生产任务!");  
                    try  
                    {  
                        // 由于条件不满足,生产阻塞  
                        list.wait();  
                    }  
                    catch (InterruptedException e)  
                    {  
                        e.printStackTrace();  
                    }  
                }  
      
                // 生产条件满足情况下,生产num个产品  
                for (int i = 1; i <= num; ++i)  
                {  
                    list.add(new Object());  
                }  
      
                System.out.println("【已经生产产品数】:" + num + "	 【现仓储量为】:" + list.size());  
      
                list.notifyAll();  
            }  
        }  
      
        /**
         * 消费num个产品
         * @param num 消费产品数量
         */
        public void consume(int num)  
        {  
            // 同步代码段  
            synchronized (list)  
            {  
                // 如果仓库存储量不足  
                while (list.size() < num)  
                {  
                    System.out.println("【要消费的产品数量】:" + num + " 	【库存量】:"  
                            + list.size() + " 	 暂时不能执行生产任务!");  
                    try  
                    {  
                        // 由于条件不满足,消费阻塞  
                        list.wait();  
                    }  
                    catch (InterruptedException e)  
                    {  
                        e.printStackTrace();  
                    }  
                }  
      
                // 消费条件满足情况下,消费num个产品  
                for (int i = 1; i <= num; ++i)  
                {  
                    list.remove();  
                }  
      
                System.out.println("【已经消费产品数】:" + num + " 	 【现仓储量为】:" + list.size());  
      
                list.notifyAll();  
            }  
        }  
      
        // get/set方法  
        public LinkedList<Object> getList()  
        {  
            return list;  
        }  
      
        public void setList(LinkedList<Object> list)  
        {  
            this.list = list;  
        }  
      
        public int getMAX_SIZE()  
        {  
            return MAX_SIZE;  
        }  
    }  

    2、生产者

    /** 
     * 生产者类Producer继承线程类Thread 
     *  
     *  
     * @author zcr
     *  
     */  
    public class Producer extends Thread  
    {  
        // 每次生产的产品数量  
        private int num;  
      
        // 所在放置的仓库  
        private Storage storage;  
      
        // 构造函数,设置仓库  
        public Producer(Storage storage)  
        {  
            this.storage = storage;  
        }  
      
        // 线程run函数  
        public void run()  
        {  
            produce(num);  
        }  
      
        // 调用仓库Storage的生产函数  
        public void produce(int num)  
        {  
            storage.produce(num);  
        }  
      
        // get/set方法  
        public int getNum()  
        {  
            return num;  
        }  
      
        public void setNum(int num)  
        {  
            this.num = num;  
        }  
      
        public Storage getStorage()  
        {  
            return storage;  
        }  
      
        public void setStorage(Storage storage)  
        {  
            this.storage = storage;  
        }  
    }  

    3、消费者

    /** 
     * 消费者类Consumer继承线程类Thread 
     *  
     *  
     * @author zcr
     *  
     */  
    public class Consumer extends Thread  
    {  
        // 每次消费的产品数量  
        private int num;  
      
        // 所在放置的仓库  
        private Storage storage;  
      
        // 构造函数,设置仓库  
        public Consumer(Storage storage)  
        {  
            this.storage = storage;  
        }  
      
        // 线程run函数  
        public void run()  
        {  
            consume(num);  
        }  
      
        // 调用仓库Storage的生产函数  
        public void consume(int num)  
        {  
            storage.consume(num);  
        }  
      
        // get/set方法  
        public int getNum()  
        {  
            return num;  
        }  
      
        public void setNum(int num)  
        {  
            this.num = num;  
        }  
      
        public Storage getStorage()  
        {  
            return storage;  
        }  
      
        public void setStorage(Storage storage)  
        {  
            this.storage = storage;  
        }  
    }  

    4、测试类

    /** 
     * 测试类Test 
     * @author zcr
     *  
     */  
    public class Test  
    {  
        public static void main(String[] args)  
        {  
            // 仓库对象  
            Storage storage = new Storage();  
      
            // 生产者对象  
            Producer p1 = new Producer(storage);  
            Producer p2 = new Producer(storage);  
            Producer p3 = new Producer(storage);  
            Producer p4 = new Producer(storage);  
            Producer p5 = new Producer(storage);  
            Producer p6 = new Producer(storage);  
            Producer p7 = new Producer(storage);  
      
            // 消费者对象  
            Consumer c1 = new Consumer(storage);  
            Consumer c2 = new Consumer(storage);  
            Consumer c3 = new Consumer(storage);  
      
            // 设置生产者产品生产数量  
            p1.setNum(10);  
            p2.setNum(10);  
            p3.setNum(10);  
            p4.setNum(10);  
            p5.setNum(10);  
            p6.setNum(10);  
            p7.setNum(80);  
      
            // 设置消费者产品消费数量  
            c1.setNum(50);  
            c2.setNum(20);  
            c3.setNum(30);  
      
            // 线程开始执行  
            c1.start();  
            c2.start();  
            c3.start();  
            p1.start();  
            p2.start();  
            p3.start();  
            p4.start();  
            p5.start();  
            p6.start();  
            p7.start();  
        }  
    } 

    5、结果:

    【要消费的产品数量】:50     【库存量】:0      暂时不能执行生产任务!
    【要消费的产品数量】:20     【库存量】:0      暂时不能执行生产任务!
    【已经生产产品数】:10     【现仓储量为】:10
    【要消费的产品数量】:20     【库存量】:10      暂时不能执行生产任务!
    【要消费的产品数量】:50     【库存量】:10      暂时不能执行生产任务!
    【已经生产产品数】:10     【现仓储量为】:20
    【已经生产产品数】:10     【现仓储量为】:30
    【要消费的产品数量】:50     【库存量】:30      暂时不能执行生产任务!
    【已经消费产品数】:20      【现仓储量为】:10
    【已经生产产品数】:10     【现仓储量为】:20
    【已经生产产品数】:10     【现仓储量为】:30
    【要消费的产品数量】:50     【库存量】:30      暂时不能执行生产任务!
    【已经生产产品数】:10     【现仓储量为】:40
    【已经消费产品数】:30      【现仓储量为】:10
    【要消费的产品数量】:50     【库存量】:10      暂时不能执行生产任务!
    【已经生产产品数】:80     【现仓储量为】:90
    【已经消费产品数】:50      【现仓储量为】:40

      看完上述代码,对wait() / notify()方法实现的同步有了了解。你可能会对Storage类中为什么要定义public void produce(int num);和public void consume(int num);方法感到不解,为什么不直接在生产者类Producer和消费者类Consumer中实现这两个方法,却要调用Storage类中的实现呢?淡定,后文会有解释。我们先往下走。


    二、await() / signal()方法

      在JDK5.0之后,Java提供了更加健壮的线程处理机制,包括同步、锁定、线程池等,它们可以实现更细粒度的线程控制。await()和signal()就是其中用来做同步的两种方法,它们的功能基本上和wait() / nofity()相同,完全可以取代它们,但是它们和新引入的锁定机制Lock直接挂钩,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全

      下面来看代码:  

      只需更新仓库类Storage的代码即可,生产者Producer、消费者Consumer、测试类Test的代码均不需要进行任何更改。

    仓库类

    import java.util.LinkedList;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 仓库类Storage实现缓冲区
     * 
     * 
     * @author MONKEY.D.MENG 2011-03-15
     * 
     */
    public class Storage
    {
        // 仓库最大存储量
        private final int MAX_SIZE = 100;
    
        // 仓库存储的载体
        private LinkedList<Object> list = new LinkedList<Object>();
    
        //
        private final Lock lock = new ReentrantLock();
    
        // 仓库满的条件变量
        private final Condition full = lock.newCondition();
    
        // 仓库空的条件变量
        private final Condition empty = lock.newCondition();
    
        // 生产num个产品
        public void produce(int num)
        {
            // 获得锁
            lock.lock();
    
            // 如果仓库剩余容量不足
            while (list.size() + num > MAX_SIZE)
            {
                System.out.println("【要生产的产品数量】:" + num + "/t【库存量】:" + list.size()
                        + "/t暂时不能执行生产任务!");
                try
                {
                    // 由于条件不满足,生产阻塞
                    full.await();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
    
            // 生产条件满足情况下,生产num个产品
            for (int i = 1; i <= num; ++i)
            {
                list.add(new Object());
            }
    
            System.out.println("【已经生产产品数】:" + num + "/t【现仓储量为】:" + list.size());
    
            // 唤醒其他所有线程
            full.signalAll();
            empty.signalAll();
    
            // 释放锁
            lock.unlock();
        }
    
        // 消费num个产品
        public void consume(int num)
        {
            // 获得锁
            lock.lock();
    
            // 如果仓库存储量不足
            while (list.size() < num)
            {
                System.out.println("【要消费的产品数量】:" + num + "/t【库存量】:" + list.size()
                        + "/t暂时不能执行生产任务!");
                try
                {
                    // 由于条件不满足,消费阻塞
                    empty.await();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
    
            // 消费条件满足情况下,消费num个产品
            for (int i = 1; i <= num; ++i)
            {
                list.remove();
            }
    
            System.out.println("【已经消费产品数】:" + num + "/t【现仓储量为】:" + list.size());
    
            // 唤醒其他所有线程
            full.signalAll();
            empty.signalAll();
    
            // 释放锁
            lock.unlock();
        }
    
        // set/get方法
        public int getMAX_SIZE()
        {
            return MAX_SIZE;
        }
    
        public LinkedList<Object> getList()
        {
            return list;
        }
    
        public void setList(LinkedList<Object> list)
        {
            this.list = list;
        }
    }

    结果:

    【要消费的产品数量】:50/t【库存量】:0/t暂时不能执行生产任务!
    【已经生产产品数】:10/t【现仓储量为】:10
    【已经生产产品数】:10/t【现仓储量为】:20
    【要消费的产品数量】:30/t【库存量】:20/t暂时不能执行生产任务!
    【已经消费产品数】:20/t【现仓储量为】:0
    【已经生产产品数】:10/t【现仓储量为】:10
    【已经生产产品数】:10/t【现仓储量为】:20
    【要消费的产品数量】:50/t【库存量】:20/t暂时不能执行生产任务!
    【已经生产产品数】:10/t【现仓储量为】:30
    【已经生产产品数】:10/t【现仓储量为】:40
    【要生产的产品数量】:80/t【库存量】:40/t暂时不能执行生产任务!
    【已经消费产品数】:30/t【现仓储量为】:10
    【要消费的产品数量】:50/t【库存量】:10/t暂时不能执行生产任务!
    【已经生产产品数】:80/t【现仓储量为】:90
    【已经消费产品数】:50/t【现仓储量为】:40

      

      这样我们就知道为神马我要在Storage类中定义public void produce(int num);和public void consume(int num);方法,并在生产者类Producer和消费者类Consumer中调用Storage类中的实现了吧。将可能发生的变化集中到一个类中,不影响原有的构架设计,同时无需修改其他业务层代码。


    总结

      两种方式原理一致,都是对独占空间加锁,阻塞和唤醒线程,第一种方式比较传统,第二种方式速度比较快。

      致谢:感谢您的耐心阅读!

  • 相关阅读:
    Effective Java 第三版——72. 赞成使用标准异常
    Effective Java 第三版——71. 避免不必要地使用检查异常
    Effective Java 第三版——70. 对可恢复条件使用检查异常,对编程错误使用运行时异常
    Effective Java 第三版——69. 仅在发生异常的条件下使用异常
    Effective Java 第三版——68. 遵守普遍接受的命名约定
    Effective Java 第三版——67. 明智谨慎地进行优化
    Effective Java 第三版——66. 明智谨慎地使用本地方法
    Effective Java 第三版——65. 接口优于反射
    Effective Java 第三版——64. 通过对象的接口引用对象
    Effective Java 第三版——63. 注意字符串连接的性能
  • 原文地址:https://www.cnblogs.com/0201zcr/p/4758533.html
Copyright © 2011-2022 走看看