阻塞队列,消息队列

文章介绍了阻塞队列的概念,包括其在队列为空或满时的阻塞机制。消息队列在此基础上增加了消息类型的处理,常用于分布式系统。生产者消费者模型通过阻塞队列实现线程间的协作,降低了系统组件之间的耦合,有利于系统的稳定性和扩展性。文中还提供了Java实现的生产者消费者模型示例代码,并讨论了如何实现一个简单的阻塞队列。
摘要由CSDN通过智能技术生成

目录

阻塞队列

消息队列

生产者消费者模型


阻塞队列

队列是一个先进先出的数据结构,阻塞队列是带有特殊功能的队列

1.如果队列为空,执行出队列操作,就会阻塞,阻塞到另一个线程往队列里添加元素(队列不空)为止。2.如果队列满了,执行入队列操作,也会阻塞,阻塞到另一个线程从队列取走元素位置(队列不满)

消息队列

消息队列也是特殊的队列,相当于是在阻塞队列的基础上,加上了个“消息的类型”按照制定类型进行先进先出。比如学校组织在医院体检时做b超时,我们首先要排队,但是能检查胃,心脏等等,护士叫患者的时候,会说:下一个,来一个检查心脏的,我们进行先进先出的时候,同时也按一定的类型先进先出,把这个消息队列实现为程序,这个程序,可以通过网络的方式和其他程序进行通信,这是这个消息队列,就可以单独部署到一组服务器上(分布式),存储能力和转发能力都大大提升了。因此消息队列已经成为了和mysql相提并论的一个重要组件“中间件”了

生产者消费者模型

就类似于包饺子:1.每个人分别进行擀饺子皮+包饺子操作2.一个人专门负责擀饺子皮,另外三个人负责包,我每次擀好一个饺子皮,就放到盖帘上,他们每次都从盖帘上取一个皮进行包,第一种,大家会竞争擀面杖,就会产生阻塞等待,影响效率,第二种就是我负责擀饺子皮,我就是生产者,他们三个负责包,就是消费者,盖帘就是阻塞队列,如果我擀的太慢了,他们就得等,如果我擀的太快了,我就得等一会。擀饺子皮的人不关心包饺子的人是谁,不管是机器包还是手工包,包饺子的人也不管擀饺子皮的人是谁。

好处1.实现了发送方和接收方之间的“解耦” 降低耦合的过程,就叫做“解耦”,代码的要求就是要追求“低耦合”,开发中典型的场景:服务器之间的相互调用

此时A把请求给了B,B处理完有返回给A,此时A和B的耦合时比较高的,A调用B,A无比要知道B 的存在,如果B出问题了,很容易引起A的bug,如果再加一个C服务器,此时要对A修改不少的代码,非常麻烦,此时,如果使用生产者消费者模型,就可以有效的降低耦合 

  此时A 和B的耦合已经降低很多了,A不知道B的存在,B也不知道A的存在,A中没有一行代码时和B相关的,如果B出问题,对A没有任何影响,,因为队列还好着,A依然可以给队列插入元素,如果队列满了,就阻塞就可以了,如果新增一个C来作为消费者,对A来说也时没有任何影响的

2.可以“削(xue)峰填谷”,保证系统的稳定性

三峡大坝就好比阻塞队列,削峰填谷,如果上游水多了,就关闭蓄水,水少了就开闸放水,对下游起到了很好的保护作用。对上游什么时候涨水什么时候水少是不知道的,上游就是用户发送的请求,下游就是执行任务的服务器,用户发送多少请求,我们是不知道的,所以就要使用生产者消费者模型,来做好准备。

生产者消费者代码实现

import java.beans.IntrospectionException;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;

public class ThreadDemo {
    public static void main(String[] args) {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();

        //创建两个线程 来作为生产者和消费者
        Thread customer = new Thread(() ->{
            while (true) {
                try {
                    Integer result = blockingQueue.take();
                    System.out.println("消费元素"+result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        customer.start();

        Thread prouducer = new Thread(()->{
            int count = 0;
              while(true){
                  try{
                      blockingQueue.put(count);
                      System.out.println("生产元素"+count);
                      count++;
                      Thread.sleep(500);//每隔500秒生产一个
                  }catch (InterruptedException e){
                      e.printStackTrace();
                  }
              }
        });
        prouducer.start();
    }
}

每生产一个就会消费一个,但是生产线程和消费线程是并发执行的,所以谁先启动是都有可能的,整体来说是成对出现的

实现一个阻塞队列

要实现一个阻塞队列,需要先写一个普通的队列,可以基于链表,也可以基于数组,基于数组恒容易进行头删尾插,一个链表的头删操作,时间复杂度是o(1),尾插操作也可以是o(1),只要用一个额外的引用,记录当前的尾节点即可,头插尾删也是可以的。同样也可以基于环形数组,默认情况,这个数组每个元素虽然已经开辟了内存空间,但是视为上面的元素是无效元素,构成一个前闭后开[head,tail),添加一个元素,tail++;head和tail重合可能是空也可能是满,我们可以浪费一个元素,或者引入一个size,来记录个数。

阻塞功能意味着,在单线程下,如果队列为空,执行出队列操作,那么就会阻塞,既然自己阻塞了,就没办法去添加元素,所以队列要在多线程坏境下使用,那么我们首先要保证线程安全。如果对两个线程同时修改一个变量就会出现线程安全问题,所以我们要对线程加上锁synchronized(this)保证线程安全,使用this表示针对同一个队列去put和take的时候会出现锁竞争,针对不同队列没有锁竞争,两个wait不会同时触发,if条件不同,但是wait被唤醒的时候,此时的if的条件,此时if的条件,一定就不成立了吗?具体来说,put中的wait被唤醒,要求队列不满,但是外套被唤醒了之后,队列一定是不满的吗?当前的代码确实不会出现这种情况,当前代码一定是去元素成功才唤醒,每次去元素都会唤醒,但稳妥起见,最好的办法是wait返回之后再判定一下,看此时的条件是不是具备了,所以将if换为while。就类似于早上起床,要定个闹钟,定个九点的闹钟,正常情况下,闹钟响了,就该起床了,但是如果七点就醒了,就发现,醒早了(条件还不满足),那我就可以再睡会,每次醒了都看下时间,判断一下当前是否满足起床的条件。

//自己实现一个阻塞队列
class MyBlockingQueue{
//基于数组实现
    //变量
    private int[] items = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;
    //入队列和出队列的wait不会同时触发 针对同一个队列 不能又是满的又是空的
    //入队列
    public void put(int value) throws InterruptedException  {
        synchronized (this){
        //if(size == items.length)
        while(size == items.length) {
            //return
     //队列满了 此时要产生阻塞,不能继续插入,
     // 阻塞到另一个元素出去,所以就要加入wait notify
            this.wait();
        }
        items[tail] = value;
        tail++;

        //对tail的处理
        //1)
        //tail = tail % items.length;
        //2)可读性更好 这个代码的效率可能比%更高
        if (tail >= items.length) {
            tail = 0;
        }
        size++;
        //这个notify唤醒take 中的wait
        this.notify();
      }
    }
    public Integer take()throws InterruptedException {
        int result = 0;
        synchronized (this) {
          //if(size == 0)
           while (size == 0) {
                //队列为空,不能出队列
               // return null;
              this.wait();
            }
             result = items[head];
            head++;
            if (head >= items.length) {
                head = 0;
            }
            size--;
            //唤醒put中的wait
            this.notify();
        }
        return result;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值