多线程】多线程中的生产者消费者模型的作用是什么

2020-09-23 08:03发布

5条回答
不爱编程的刘老师
2楼 · 2020-09-23 08:04

(1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用


(2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约

无需指教
3楼 · 2020-09-23 08:31

题目如下:
      在生产者/消费者模型中,生产者Producer负责生产数据,而消费者Consumer负责使用数据。多个生产者线程会在同一时间运行,生产数据,并放到内存中一个共享的区域。期间,多个消费者线程读取内存共享区,消费里面的数据。
     

分析

在下面Java应用程序中,生产者线程向一个线程安全的堆栈缓冲区中写(PUSH)数据,消费者从该堆栈缓冲区中读(POP)数据,这样,这个程序中同时运行的两个线程共享同一个堆栈缓冲区资源。

类Producer是生产者模型,其中的run方法中定义了生产者线程所做的操作,循环调用push()方法,将生产的100个字母送入堆栈中,每次执行完push操作后,调用sleep方法睡眠一段随机时间。

类Consumer是消费者模型,循环调用pop方法,从堆栈取出一个字母,一共取100次,每次执行完push操作后,调用sleep方法睡眠一段随机时间

借鉴《Java多线程-并发协作(生产者消费者模型)》

同步堆栈类SynchronizedStack

复制代码

package cn.thread;/**
 * 同步堆栈类
 * 
 * @author 林计钦
 * @version 1.0 2013-7-24 下午02:03:21 */public class SynchronizedStack {    private int index = 0;    private int size = 100;    // 内存共享区
    private char[] data;    public SynchronizedStack(int size) {
        System.out.println("栈被创建");        this.size = size;
        data = new char[size];
    }    /**
     * 生产数据
     * 
     * @param c     */
    public synchronized void push(char c) {        while (index == size) {            try {
                System.err.println("栈满了");                this.wait();// 等待,直到有数据出栈
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                e.printStackTrace();
            }
        }
        data[index] = c;
        index++;        this.notify();// 通知其它线程把数据出栈    }    /**
     * 消费数据
     * 
     * @return
     */
    public synchronized char pop() {        while (index == 0) {            try {
                System.err.println("栈空了");                this.wait();// 等待,直到有数据出栈
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                e.printStackTrace();
            }
        }
        index--; // 指针向下移动
        char ch = data[index];        this.notify(); // 通知其它线程把数据入栈
        return ch;
    }    // 显示堆栈内容
    public synchronized void print() {        for (int i = 0; i < data.length; i++) {
            System.out.print(data[i]);
        }
        System.out.println();        this.notify(); // 通知其它线程显示堆栈内容    }
}

复制代码

生产者Product

复制代码

package cn.thread;/**
 * 生产者
 * 
 * @author 林计钦
 * @version 1.0 2013-7-24 下午02:14:14 */public class Producer implements Runnable {    private SynchronizedStack stack;    public Producer(SynchronizedStack s) {
        stack = s;
    }    public void run() {        char ch;        for (int i = 0; i < 100; i++) {            // 随机产生100个字符
            ch = (char) (Math.random() * 26 + 'A');
            stack.push(ch);
            System.out.println("Produced:" + ch);            try {                // 每产生一个字符线程就睡眠一下
                Thread.sleep((int) (Math.random() * 1000));
            } catch (InterruptedException e) {
                
            }
        }
    }

}

复制代码

消费者Consumer

复制代码

package cn.thread;/**
 * 消费者
 * 
 * @author 林计钦
 * @version 1.0 2013-7-24 下午02:15:43 */public class Consumer implements Runnable {    private SynchronizedStack stack;    public Consumer(SynchronizedStack s) {
        stack = s;
    }    public void run() {        char ch;        for (int i = 0; i < 100; i++) {
            ch = stack.pop();
            System.out.println("Consumed:" + ch);            try {                // 每产生一个字符线程就睡眠一下
                Thread.sleep((int) (Math.random() * 1000));
            } catch (InterruptedException e) {
            }
        }
    }

}

复制代码

测试类

复制代码

package cn.thread;/**
 * 
 * 
 * @author 林计钦
 * @version 1.0 2013-7-24 下午02:18:36 */public class ProductConsumerTest {    public static void main(String[] args) {        // 下面的消费者类对象和生产者类对象所操作的是同一个同步堆栈对象
        SynchronizedStack stack = new SynchronizedStack(5);
        Runnable source = new Producer(stack);
        Runnable sink = new Consumer(stack);

        Thread t1 = new Thread(source);
        Thread t2 = new Thread(sink);
        t1.start();
        t2.start();
    }
}

复制代码

 

在本例中,使用了一个生产者线程和一个消费者线程,当生产者线程往堆栈中添加字符时,如果堆栈已满,通过调用this.wait方法,(这里,this就是互斥锁)把自己加入到互斥锁对象(SynchronizedStack)的锁等待队列中,如果该堆栈不满,则该生产者线程加入到互斥锁对象(SynchronizedStack)的锁申请队列中,并且很快就被JVM取出来执行。当生产者线程在执行添加字符操作的时候,消费者是不能从中取出字符的,只能在等待队列中等待,当生产者添加完字符的时候,使用this.notify()(这里,this就是互斥锁)把等待队列中的第一个消费者唤醒,把它加入到锁申请队列中,很快该消费者线程就会获得CPU时间。此时的生产者线程已经无法再次添加字符,因为消费者线程正在synchronized代码块中运行,JVM把生产者线程加入锁等待队列中。当消费者线程从堆栈中取完字符后,再使用this.notify()方法把生产者从等待进程中唤醒,添加字符,如此循环往复,直到生产者线程和消费者线程都运行结束。

              

下面是该程序的某次运行结果:

复制代码

栈被创建
栈空了
Produced:W
Consumed:W
栈空了
Produced:C
Consumed:C
栈空了
Produced:R
Consumed:R
栈空了
Produced:R
Consumed:R
Produced:Z
Consumed:Z
Produced:A
Consumed:A
Produced:W
Consumed:W
Produced:P
Consumed:P
Produced:C
Consumed:C
Produced:X
Consumed:X
栈空了
Produced:Q
Consumed:Q
栈空了
Consumed:R
Produced:R
Produced:S



小猪仔
4楼 · 2021-01-22 10:02

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

超甜的布丁
5楼 · 2021-12-09 12:08

为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。


慢半拍
6楼 · 2021-12-14 11:36

1、解耦

假设生产者和消费者分别是两个类。

如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。

将来如果消费者的代码发生变化, 可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。

举个例子,我们去邮局投递信件,如果不使用邮筒(也就是缓冲区),你必须得把信直接交给邮递员。

有同学会说,直接给邮递员不是挺简单的嘛?其实不简单,你必须 得认识谁是邮递员,才能把信给他(光凭身上穿的制服,万一有人假冒,就惨了)。

这就产生和你和邮递员之间的依赖(相当于生产者和消费者的强耦合)。

万一哪天邮递员换人了,你还要重新认识一下(相当于消费者变化导致修改生产者代码)。

而邮筒相对来说比较固定,你依赖它的成本就比较低(相当于和缓冲区之间的弱耦合)。

2、支持并发

由于生产者与消费者是两个独立的并发体,他们之间是用缓冲区作为桥梁连接,生产者只需要往缓冲区里丢数据,

就可以继续生产下一个数据,而消费者只需要从缓冲区了拿数据即可,这样就不会因为彼此的处理速度而发生阻塞。

接上面的例子,如果我们不使用邮筒,我们就得在邮局等邮递员,直到他回来,

我们把信件交给他,这期间我们啥事儿都不能干(也就是生产者阻塞),或者邮递员得挨家挨户问,谁要寄信(相当于消费者轮询)。

3、支持忙闲不均

缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。

当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。 等生产者的制造速度慢下来,消费者再慢慢处理掉。

为了充分复用,我们再拿寄信的例子来说事。假设邮递员一次只能带走1000封信。万一某次碰上情人节(也可能是圣诞节)送贺卡,

需要寄出去的信超过1000封,这时 候邮筒这个缓冲区就派上用场了。邮递员把来不及带走的信暂存在邮筒中,等下次过来 时再拿走。


相关问题推荐

  • 回答 7

    没有所谓的核心线程,线程只有单线程和多线程之分~首先,关于计算机系统的很多概念,都有逻辑层 和 物理层的区分,这个是前提。然后再看,核心这个概念是物理层的概念,指的就是 CPU硬件的物理核心数量。而线程 这个概念,是逻辑层的概念,而且这个逻辑层的概...

  • 回答 2

    groupNum = / List listThreads = ArrayList()(i = i 

  • 回答 4

    多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程...

  • 回答 18

    这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被 new这个线程类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用 的。如果说上面的说法让你感到困惑,那么我举个例子,假设 Thread2 中 new 了 Thread1,main ...

  • 回答 26

    多线程类似于同时执行多个不同程序,多线程运行有如下优点:使用线程可以把占据长时间的程序中的任务放到后台去处理。用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度程序的运行速度可能加快在...

  • 回答 7

    同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。...

  • 回答 5

    单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。单例模式的特点:(1)、在任何情况下,单例类永远只有一个实例存在。(2)、单例需要有能力为整个系统提供这一唯一实例。...

  • 回答 5
    已采纳

    很多人都知道死锁是怎么一回事儿:线程A和线程B相互等待对方持有的锁导致程序无限死循环下去。当然也仅限于此了,问一下怎么写一个死锁的程序就不知道了,这种情况说白了就是不懂什么是死锁,懂一个理论就完事儿了,实践中碰到死锁的问题基本上是看不出来的。...

  • 回答 20

    100-199 用于指定客户端应相应的某些动作。 200-299 用于表示请求成功。 300-399 用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息。 400-499 用于指出客户端的错误。 400 语义有误,当前请求无法被服务器理解。 401 当前请求需要用户验证...

  • 回答 5
    已采纳

    1、相同点(1)都是表现层框架,都是基于MVC设计模型(2)底层都离不开 Servlet API(3)处理请求的机制都是一个核心控制器2、不同点(1)SpringMVC的入口是Servlet,而Struts2的入口是Filter(2)SpringMVC是基于方法设计的,而Struts2是基于类(3)SpringMV...

  • 回答 22
    已采纳

    类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结...

  • 回答 23
    已采纳

    (1)idea启动时会有两个快捷方式,安装完后默认生成在桌面的是32位的idea的快捷方式,如果我们使用这个快捷方式运行大项目,一般都会很卡。解决方法是找到idea的安装目录,然后进入bin文件夹,找到名称为idea64的应用程序,右键他生成桌面快捷方式。以后每次...

  • 回答 12
    已采纳

    获取Map集合中所有的key可以通过map集合的keySet()方法获取例如:    Map map = new HashMap();    map.put(xx,xx); //存放数据    //.... 省略    Set set = map.keySet();    //可以通过迭代器进行测试    Iterator iter = set.iter...

  • 回答 4
    已采纳

    Java中有八种数据类型,基础数据类型分别是:byte,short,int,long,float,double,char,boolean,引用数据类型分别是:数组,类和接口。方法传参的时候我们有两种,一种是形式参数(定义方法时写的参数),一种是实际参数(调用方法时给的具体值)。首先...

  • 回答 15
    已采纳

    现在的架构很多,各种各样的,如高并发架构、异地多活架构、容器化架构、微服务架构、高可用架构、弹性化架构等,还有和这些架构相关的管理型的技术方法,如 DevOps、应用监控、自动化运维、SOA 服务治理、去 IOE 等等,还有很多。分布式架构其实就是分布式系...

  • 回答 10

    1、监控GC的状态使用各种JVM工具,查看当前日志,分析JVM参数的设置,分析堆内存快照和GC日志,根据实际的各区域的内存划分和GC的执行时间,判断是否需要进行优化2、分析结果、判断是否需要优化如果各项参数设置合理,系统没有超时的日志出现,GC频率也不高,...

没有解决我的问题,去提问