JAVAEE-7-阻塞队列

阻塞队列

阻塞队列是基于普通队列做出的扩展(先进先出)

1.线程安全的

2.具有阻塞特性

a)如果针对一个已经满了的队列进行入队列,那么入队操作就会阻塞,一直阻塞到队列不满(其他线程出队列元素)之后

b)如果针对一个已经空了的队列进行出兑列,此时出队列操作就会阻塞,一直阻塞到队列不空(其他线程引入元素)之后

基于阻塞队列,就可以实现"生产者消费者模型"---------------------->描述的是一种多线程编程的思想

生产者消费者模型

1.引入生产者消费者模型,我们可以做到更好的"解耦和"(把代码的耦合程度从高降低~~就称为哦和解耦合)

实际开发中,经常会涉及到:分布式系统,服务器整个功能并不是由一个服务器全部完成的,而是每个服务器负责一部分功能,通过服务器之间的网络通信,最终完成整个功能~~

上述模型中,A和B,A和C之间的耦合程度较高,

A中的代码涉及B和C的一些相关操作,B和C也设计A的一些操作,

此时,如果B或C挂了,对A的影响就很大了,A很有可能就也挂了,此时,如果我们想要再增加一个D服务器功能,就需要在A的基础上再做修改,这样就会越改越乱,那么此时,我们就可以引入生产者消费者模型来解决这个问题,通过引入阻塞队列,构建生产者消费者模型来去解决这个问题.

此时,A和B,A和C之间就不用直接进行交互了,而是通过阻塞队列进行交互,A不知道B和C的存在,B和C也不知道A的存在,它们只需要跟阻塞队列进行交互就可以.

这种情况下,如果B和C挂了,对A几乎没有影响.

如果后续需要增加一个服务器D,A的代码也几乎不需要做改变.

使用这种模式需要加入更多的机器,

一:引入更多的硬件资源,

1)上述描述的阻塞队列,并不是简单的数据结构,而是基于这个数据结构实现的服务器程序,又被部署到单独的主机上去了.(消息队列(message queue,mq))

2)整个系统的结构更复杂了,你要维护的服务器更多了.

3)效率,引入中间的这个阻塞队列,请求从A出发发送给B,这个过程需要经过阻塞队列的转发,这个过程肯定也是有一定的开销的.

二:削峰填谷

我们的服务器在处理每个请求的时候,都是需要消耗硬件资源的,包括但不限于,CPU,内存,硬盘,网络带宽.

在请求激增这种情况下,即使一个请求消耗的资源很少,但是如果同时有很多请求的话,加到一起,消耗的硬件资源就多了.上述任何一种硬件资源达到瓶颈的话~~服务器就会挂,(服务器不会返回响应了)

图中的阻塞队列没有什么业务逻辑,只是用来存储数据,抗压能力比较强,即使外界的请求出现峰值,也是由队列来承担峰值请求,B和C还是按照之前的速度来进行读取.

阻塞队列具体实现

ArrayBlockingQueue

LinkedBlockingQueu

PriorityBlockingQueue

这些类都实现了BlockingQueue这个接口interface

该队列具有put和offer两个入栈,但是put带有阻塞,offer没带阻塞(队列满了会返回结果)

阻塞队列没有提供带有阻塞功能的获取队首元素的方法.

1)普通队列

class MyBLOCKEDQUEUE{

    private String elems[]=null;//队列存储结构
    private int size=0;//当前元素数量
    private int tail=0;//队尾
    private int head=0;//队头
    public MyBLOCKEDQUEUE(int size){

        this.elems=new String[size];
    }
    public void put(String elem){
        elems[tail]=elem;
        size++;
        tail++;
        if(tail>=elems.length){
            tail=0;
        }
    }
    public String take(){
        String ret=elems[head];
        head++;
        size--;
        if(head>=elems.length){
            head=0;
        }
        return ret;
    }
}

2)线程安全

为了避免这种情况,在一个put还没有实现tail++操作的时候,另外一个线程已经进行put了,那么这样就会出现线程安全,take也是同理,所以,我们要给他加上锁.

3)阻塞功能

我们在入队列的时候进行判断,如果队列满了,就需要进入阻塞,等到队列出元素为止,

出队列的时候也要进行判断,如果队列为空,就需要在为空的时候,等到队列有元素进入停止等待.

但是,还有一种情况需要考虑,

这样就会进行错误唤醒

最终代码:

class MyBLOCKEDQUEUE{

    private String elems[]=null;//队列存储结构
    private int size=0;//当前元素数量
    private int tail=0;//队尾
    private int head=0;//队头
    private Object locker=new Object();
    public MyBLOCKEDQUEUE(int size){

        this.elems=new String[size];
    }
    public void put(String elem) throws InterruptedException {
        synchronized (locker) {
            while (size>= elems.length){//被唤醒之后需要再次进行判断,看是否满足条件,防止被错误唤醒
                locker.wait();
            }
            elems[tail] = elem;
            size++;
            tail++;
            if (tail >= elems.length) {
                tail = 0;
            }
        }
        locker.notify();
    }
    public String take() throws InterruptedException {
        synchronized (locker) {
            while (size==0){
                locker.wait();
            }
            String ret = elems[head];
            head++;
            size--;
            if (head >= elems.length) {
                head = 0;
            }
            locker.notify();
            return ret;
        }
    }
}

一般wait都是搭配while循环来进行使用的

我们可以使用阻塞队列来实现基本的一个生产者消费者模型

class MyBlockingQueue{
    private String []elem=null;
    private int size;
    private int head;
    private int tail;
    private Object locker=new Object();
    public MyBlockingQueue(int size){
        elem=new String[size];
        this.size=0;
    }
    public void put(String s) throws InterruptedException {
        synchronized (locker){
            while(size>=elem.length){
                locker.wait();
                //进入阻塞状态
            }

            elem[tail]=s;
            size++;
            tail++;
            if(tail>=elem.length){
                tail=0;

            }
            locker.notify();
        }

    }
    public String take() throws InterruptedException {
        String ret;
      synchronized (locker){
          while(size<=0){//有可能被
              locker.wait();
              //进入阻塞状态
          }

           ret=elem[head];
          head++;
          size--;
          if(head>=elem.length){
              head=0;
          }
          locker.notify();
      }
        return ret;
    }
}
public class ThreadDemon24 {
    public static void main(String[] args) {
        MyBlockingQueue queue=new MyBlockingQueue(100);
        Thread t1=new Thread(()->{
            int count=0;
            while(true){

                try {
                    queue.put(""+count);
                    System.out.println("生产商品"+count);
                    count++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });



        Thread t2=new Thread(()->{
            while(true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                try {
                    String t=queue.take();
                    System.out.println("消费商品"+t);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

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



}

部分运行结果

实际开发中,生产者消费者往往是多个生产者消费者,这里的生产者和消费者往往不仅仅是一个线程,也可能是一个独立的服务器程序,甚至是一组服务器程序.

在这值周,最核心的部分依旧是阻塞队列,(使用synchronized和wait/notify)达到线程安全&阻塞

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值