生产者-消费者模式

1.1 解决什么问题

        生产者消费者模式的一个典型应用场景就是“分布式系统”。那什么是分布式系统呢?

        在程序有很多很多用户访问的时候,一个服务器的资源就可能不够用,这个时候可以用多个服务器来拆分功能配合使用,每个服务器来负责一部分功能,通过通信来和其他服务器进行配合工作,单个服务器实现的功能少了,那么消耗的资源也就少了。这种多个服务器互相配合完成所有功能,就是“分布式系统”。

       如图就是一个简易版本的分布式系统,由客户端发送请求给服务器a,服务器a依靠服务器b,c响应的数据来完成整个功能,服务器a就叫消费者,服务器b、c就叫生产者。

        简而言之,提供数据的就是生产者,使用生产者提供数据来完成功能的就是消费者。

       在这种情况下,生产者和消费者都是直接通信的,服务器a高度依赖服务器b,c来完成全部功能。如果服务器b挂了,那么服务器a立刻就不能打视频电话了,如果服务器c要升级功能了,那么服务器a也立刻就不能提供看帖子的服务了。这时,服务器a和服务器b,c高度耦合。

      高度耦合,就是a高度依赖b,如果b出了什么变化会影响到a。举个例子吧,只用电锅做饭的人高度依赖电网供电。如果有一天电网维修断电了,这个电锅通不了电,就做不了饭了。在计算机中高度耦合并不是一个良好的特性。我们要追求的是高内聚,低耦合。高内聚和低耦合其实都是一个意思,就是不要去过度的依赖一个东西。

       生产者-消费者模型就是使用容器来降低生产者和消费者耦合程度。

       我们可以在生产者和消费者之间加一个阻塞队列(也可以是用阻塞队列实现的程序,或者是部署了阻塞队列的服务器),让生产者和消费者通过队列来进行通信,消费者把请求发送到队列里,消费者从队列中拿生产者发送的数据,生产者从队列中获取请求,把返回的响应发送到队列里。生产者和消费者不直接通信。这样就可以使生产者和消费者解耦。

1.2 阻塞队列

阻塞队列是一种数据结构,在队列(先进先出)的基础上增加了阻塞特性,阻塞特性就是:

  1. 在队列满的时候,入队列操作会进入阻塞,当队列不满的时候再解除阻塞,继续入队列。

  2. 在队列空的时候,出队列操作对进入阻塞,等队列不空的时候再解除阻塞,继续出队列。

     java中实现阻塞队列的类有很多,它们都实现了java.util.concurrent包下的BlockingQueue接口。下面来模拟实现一下基于数组实现的阻塞队列(在java中的类是ArrayBlockingQueue):

注意,notify方法是无差别唤醒,只要同一个锁对象里的操作进入阻塞,notify就能唤醒,所以有的时候线程被唤醒,不是因为等待的条件满足了,而是因为被“误伤"了,所以需要再次进行条件判断。所以wait往往搭配while来进行使用。

出队列和入队列的锁对象必须相同,因为这样才能构成锁竞争,保证线程安全。

1.3 实现方式

生产者就是生产数据的那一方,消费者就是需要使用生产者生产的数据的一方。生产者消费者在实际中有可能是线程,有可能是一个独立的服务器程序,还有可能是一组独立的服务器程序。

实现生产者消费者模型,就是将生产者生产的数据放入阻塞队列里,当阻塞队列满了,生产者就不能向阻塞队列放数据,当有元素从阻塞队列删除的时候,生产者就可以继续放数据了。消费者从阻塞队列中取生产者生产的数据使用,当阻塞队列为空的时候,消费者不能从阻塞队列取数据,当阻塞中添加数据的时候,消费者被就可以继续取数据了。

   

public static void main(String[] args) throws InterruptedException {
    MyBlookingQueue queue = new MyBlookingQueue(1000);


    // 生产者
    Thread t1 = new Thread(() -> {
        int n = 1;
        while (true) {
            try {
                queue.put(n + "");
                System.out.println("生产元素 " + n);
                n++;
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    });

    // 消费者
    Thread t2 = new Thread(() -> {
        while (true) {
            try {
                String n = queue.take();
                System.out.println("消费元素 " + n);

                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    });

    t1.start();
    t2.start();
}

 

       这里使用的阻塞队列是自己简单实现的,java中有阻塞队列功能的有很多类,这些类都实现了java.util.concurrent地下的BlookingQueue接口,比如有以下这些类:

  1. ArrayBlockingQueue:基于数组实现的有界阻塞队列。

  2. LinkedBlockingQueue:基于链表实现的可选有界或无界阻塞队列。

  3. PriorityBlockingQueue:支持优先级的无界阻塞队列。

  4. DelayQueue:用于存放实现了 Delayed 接口的元素,按照延迟时间顺序被消费。

  5. SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的对应移除操作,反之亦然。

  6. LinkedTransferQueue:基于链表实现的无界阻塞队列,支持更丰富的操作,如 transfer 和 tryTransfer。

 接下来用java中的LinkedBlockingQueue来实现一下:

package thread;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

public class ThreadDemo4 {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingDeque<>(10);
        Thread t1 = new Thread(() -> {
            int n = 1;
            while (true) {
                try {
                    queue.put(n);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("生产元素:" + n);
                n++;
            }
        });
        Thread t2 = new Thread(() -> {
            Integer take = null;
            while (true) {
                try {
                    Thread.sleep(2000);
                    take = queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费元素:" + take);
            }
        });
        t1.start();
        t2.start();
    }
}

1.4 实际意义

1.4.1 使生产者和消费者解耦合

生产者不需要知道消费者是谁,不关心消费者如何使用数据,消费者不需要知道生产者是谁,不关心生产者如何生产数据,生产者和消费者通过容器来进行通信。就算生产者更改生产数据的方式,也不会影响到消费者使用数据,就算消费者改变使用数据的方式,也不会影响到,生产者生产数据。

1.4.2 平衡生产者和消费者的数据处理能力(削峰填谷)

有的时候消费者取数据的速度远远大于生产者生产数据的速度,那么队列空的时候,消费者就进入阻塞状态,取不了数据,等生产者生产数据的时候才能从阻塞状态解除,继续去取数据。

有的时候生产者生产数据的速度远远大于消费者取数据的速度,那么队列满的时候,生产者就进入阻塞状态,生产不了数据,等消费者取数据的时候才能从阻塞状态解除,继续去生产数据。

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值