关闭

Java多线程14:生产者/消费者模型

标签: java多线程线程
133人阅读 评论(0) 收藏 举报
分类:

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

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

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

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

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

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

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

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

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

 

利用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时间片,等着消费者消费完通知它继续生产,这块时间片可以用来给其他线程用。

 

利用await()/signal()实现生产者和消费者模型

一样,先定义一个缓冲区:

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

换种写法,生产和消费方法放在一个类里面:

复制代码
public class ThreadDomain41 extends ReentrantLock
{
    private Condition condition = newCondition();
    
    public void set()
    {
        try
        {
            lock();
            while (!"".equals(ValueObject.value))
                condition.await();
            ValueObject.value = "123";
            System.out.println(Thread.currentThread().getName() + "生产了value, value的当前值是" + ValueObject.value);
            condition.signal();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            unlock();
        }
    }
    
    public void get()
    {
        try
        {
            lock();
            while ("".equals(ValueObject.value))
                condition.await();
            ValueObject.value = "";
            System.out.println(Thread.currentThread().getName() + "消费了value, value的当前值是" + ValueObject.value);
            condition.signal();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            unlock();
        }
    }
}
复制代码

同样的,开两个线程,一个线程调用set()方法生产,另一个线程调用get()方法消费:

复制代码
public static void main(String[] args)
{
    final ThreadDomain41 td = new ThreadDomain41();
    Runnable producerRunnable = new Runnable()
    {
        public void run()
        {
            for (int i = 0; i < Integer.MAX_VALUE; i++)
                td.set();
        }
    };
    Runnable customerRunnable = new Runnable()
    {
        public void run()
        {
            for (int i = 0; i < Integer.MAX_VALUE; i++)
                td.get();
        }
    };
    Thread ProducerThread = new Thread(producerRunnable);
    ProducerThread.setName("Producer");
    Thread ConsumerThread = new Thread(customerRunnable);
    ConsumerThread.setName("Consumer");
    ProducerThread.start();
    ConsumerThread.start();
}
复制代码

看一下运行结果:

复制代码
...
Producer生产了value, value的当前值是123
Consumer消费了value, value的当前值是
Producer生产了value, value的当前值是123
Consumer消费了value, value的当前值是
Producer生产了value, value的当前值是123
Consumer消费了value, value的当前值是
...
复制代码

和wait()/notify()机制的实现效果一样,同样符合生产者/消费者模型

 

小心假死

生产者/消费者模型最终达到的目的是平衡生产者和消费者的处理能力,达到这个目的的过程中,并不要求只有一个生产者和一个消费者。可以多个生产者对应多个消费者,可以一个生产者对应一个消费者,可以多个生产者对应一个消费者。

假死就发生在上面三种场景下。理论分析就能说明问题,所以就不写代码了。代码要写也很简单,上面的两个例子随便修改一个,开一个生产者线程/多个消费者线程、开多个生产者线程/消费者线程、开多个生产者线程/多个消费者线程都可以。假死指的是全部线程都进入了WAITING状态,那么程序就不再执行任何业务功能了,整个项目呈现停滞状态。

比方说有生产者A和生产者B,缓冲区由于空了,消费者处于WAITING。生产者B处于WAITING,生产者A被消费者通知生产,生产者A生产出来的产品本应该通知消费者,结果通知了生产者B,生产者B被唤醒,发现缓冲区满了,于是继续WAITING。至此,两个生产者线程处于WAITING,消费者处于WAITING,系统假死。

上面的分析可以看出,假死出现的原因是因为notify的是同类,所以非单生产者/单消费者的场景,可以采取两种方法解决这个问题:

1、synchronized用notifyAll()唤醒所有线程、ReentrantLock用signalAll()唤醒所有线程

2、用ReentrantLock定义两个Condition,一个表示生产者的Condition,一个表示消费者的Condition,唤醒的时候调用相应的Condition的signal()方法就可以了

0
0
查看评论

Java多线程编程核心技术——生产者消费者模型

生产者消费者模型是并发中的经典问题,具体场景是有一块缓冲区作为仓库,生产者可以向其添加产品,消费者可以从中取出产品。解决生产者消费者问题可以采用两种方式:wait()/notify方式和BlockingQueue方式,在此主要讨论第一种,关于第二种方法可以参考Ranger的Audit模型。 wait...
  • ghz_blog
  • ghz_blog
  • 2017-02-28 10:46
  • 365

Java多线程--生产者消费者模型(Semaphore实现)

需求要求:使用2个线程,分别代表:生产者、消费者。让他们并发的去生产、消费产品。生产的总数是不能超过N的。实现思路这里我们使用的是使用信号量去控制线程的生产消费,通过释放令牌的形式去控制生产者消费者的上限。使用互斥锁保证每次最多只有一个角色去修改共享变量。来看张图,一图胜千言。 代码实现代码的注释...
  • qq_24489717
  • qq_24489717
  • 2017-04-12 21:53
  • 830

【Java笔记】——多线程同步机制模拟生产者/消费者模型

上篇介绍线程,说到线程同步机制,其中说到加锁的机制,如果加锁不合理,则会产生“死锁”。如果加锁的位置合理,则会解决多线程访问同一数据的问题。多线程访问的问题,其中很典型的一个模型就是生产者/消费者模型,下面就简单介绍一下多线程同步如何模拟生产者/消费者模型。
  • u013038861
  • u013038861
  • 2015-09-15 16:52
  • 2611

线程同步经典案例-生产者与消费者模型-Java

一、相关知识简介      在生产者-消费者模型中,若只使用synchronized关键字实现对象锁,程序在运行中可能会出现以下两种情况: 若生产者的速度大于消费者,那么在消费者来不及取前一个数据之前,生产者又产生了新的数据,于是消费者很可能会跳过前...
  • Xminyang
  • Xminyang
  • 2016-11-18 15:58
  • 1024

Linux 多线程编程(实现生产者消费者模型)

Linux 多线程编程线程分类线程按照其调度者可以分为用户级线程和内核级线程两种。内核级线程在一个系统上实现线程模型的方式有好几种,因内核和用户空间提供的支持而有一定程度的级别差异。最简单的模型是在内核为线程提供了本地支持的情况,每个内核线程直接转换成用户空间的线程。这种模型称为“1:1线程模型(t...
  • wj199395
  • wj199395
  • 2017-08-27 16:41
  • 320

C++11多线程(十六):实战-生产者消费者模型

参考链接:http://www.cnblogs.com/haippy/p/3252092.html 不错的博客 目录 1.单生产者-单消费者模型 2.单生产者-多消费者模型 3.多生产者-单消费者模型 4.多生产者-多消费者模型 本节阐述经典问题:生产者消费者模型 ...
  • ceasadan
  • ceasadan
  • 2016-01-06 15:57
  • 652

JAVA多线程之生产者消费者模型

生产者消费者模型是多线程当中比较经典的一个模型,该模型模拟线程间公用同一个对象,通过调度不同的线程休眠、等待和唤醒起到预防死锁的作用。 首先列举一个线程死锁的例子,下面这个例子是模拟服务生和顾客争执先服务和先收钱而产生的死锁问题,代码如下: package deadlock; /** * 以顾客和...
  • zhuxinquan61
  • zhuxinquan61
  • 2016-06-05 10:06
  • 975

C++多线程学习:生产者消费者问题

多线程相关知识点: C++11 线程库:http://zh.cppreference.com/w/cpp/thread 互斥量和锁 std::unique_lock::lock 和 std::unique_lock::unlock 上锁操作,调用它所管理的 Mutex 对象的 lock 函数。如...
  • quzhongxin
  • quzhongxin
  • 2015-08-19 20:56
  • 5792

Java多线程之生产者消费者问题<一>:使用synchronized 关键字解决生产者消费者问题

今天看了一片博文,讲Java多线程之线程的协作,其中作者用程序实例说明了生产者和消费者问题,但我及其他读者发现程序多跑几次还是会出现死锁,百度搜了下大都数的例子也都存在bug,经过仔细研究发现其中的问题,并解决了,感觉有意义贴出来分享下。
  • feichenwangyalin
  • feichenwangyalin
  • 2014-10-16 22:22
  • 1908

生产者消费者模型你知道多少

进入正题之前先说点故事。从最开始学java的那里开始:我是从08年下半年开始学Java,在《我的六年程序之路》中提到了一些。当时比较简单,每天看尚学堂的视频(对于初学者而言看视频好一些。),然后写代码。比较清楚的记得马士兵讲到生产者消费者模型的时候还大谈特谈要是掌握了这个工资可以+1000(现在回忆...
  • luohuacanyue
  • luohuacanyue
  • 2013-11-12 08:07
  • 33817
    个人资料
    • 访问:46843次
    • 积分:835
    • 等级:
    • 排名:千里之外
    • 原创:19篇
    • 转载:112篇
    • 译文:0篇
    • 评论:3条
    文章分类
    最新评论