实现阻塞队列

一、解耦合   削峰填谷

在上图中和,我们可以看到,在A接收到信息后,分为两种情况分别传给B和C去执行.

其中,A做的工作比较简单,每个请求消耗的资源少,主要的工作由B和C去执行,

但是,这时候会出现一种情况 : 如果某一段时间内,传输的信息量暴增,达到了峰值就会导致服务器出现问题,甚至直接挂掉.

比如在以前双十一的时候,购买的用户剧增,就会导致服务器的压力变得非常打.

这时候,就可以用到阻塞队列了.

在A和B、C之间引入一个阻塞队列.

队列没什么业务逻辑,只是存储数据,抗压能力是比较强的.

即使外界的请求出现了峰值,也是由队列来承担峰值请求,B和C仍然是按照之前的速度来运行的.

引入队列,就可以有效的防止B和C出现问题了.

这就是  解耦合   和   削峰填谷.

二、Java标准库提供的阻塞队列

Java标准库提供了现成的阻塞队列数据结构.

                                             ArrayBlockingQueue

 BlockingQueue        =>        LinkedBlockingQueue

        interface                        PriorityBlockingQueue

使用put进行入队列.

put是带阻塞功能的,offer是没带阻塞功能的.( 队列满了会返回结果 )

take方法用来出队列,也是带有阻塞功能的.

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

三、实现阻塞队列

3.1 先实现普通队列

private String[] elem = null;
    private int head = 0;
    private int last = 0;
    private int size = 0;
    private Object locker = new Object();
    public MyBlockingQueue(int capcity){
        this.elem = new String[capcity];
    }
    public void put(String str) throws InterruptedException{
        if(size>=elem.length){
            //不插入
        }
        elem[last]=str;
        last++;
        if(last>= elem.length){
            last=0;
        }
        size++;
    }

    public String take() throws InterruptedException{
        String elems = null;
        if(size==0){
            //无法出队列
        }
        elems = elem[head];
        if(head>=elem.length){
            head=0;
        }
        size--;
        return elems;
    }

这就是阻塞队列基础版的阻塞队列.

3.2 加上线程安全

为了防止多线程时出现线线程安全,我们把整个方法都进行加锁.

 public void put(String str) throws InterruptedException{
        synchronized(locker) {
            if (size >= elem.length) {
                //不插入
            }
            elem[last] = str;
            last++;
            if (last >= elem.length) {
                last = 0;
            }
            size++;
        }
    }

    public String take() throws InterruptedException{
        synchronized (locker) {
            String elems = null;
            if (size == 0) {
                //无法出队列
            }
            elems = elem[head];
            if (head >= elem.length) {
                head = 0;
            }
            size--;
            return elems;
        }
    }

3.3 加上阻塞功能

最后,加上阻塞功能,使代码在执行的时候 

1.入队列时,队列为满则阻塞

2.出队列时,队列为空则阻塞

public void put(String str) throws InterruptedException{
        synchronized(locker) {
            while(size >= elem.length) {
                locker.wait();
            }
            elem[last] = str;
            last++;
            if (last >= elem.length) {
                last = 0;
            }
            size++;
            locker.notify();
        }
    }

    public String take() throws InterruptedException{
        String elems = null;
        synchronized (locker) {
            while(size == 0) {
                locker.wait();
            }
            elems = elem[head];
            if (head >= elem.length) {
                head = 0;
            }
            size--;
            locker.notify();
        }
        return elems;
    }

基于这个阻塞队列,就可以写一个 " 生产者消费者 "模型了.    ->   最核心的部分仍然是阻塞队列.

   ->   使用synchronized  和wait/notify   达到   线程安全 & 阻塞.

往往是多个生产者多个消费者.                

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

AQS(AbstractQueuedSynchronizer)是Java实现同步器的框架,它提供了一种基于FIFO队列的阻塞和唤醒机制。AQS的阻塞队列原理是通过CLH(Craig, Landin, and Hagersten)队列来实现的。 CLH队列是一种虚拟的双向链表,它仅存在节点之间的关联关系,而不存在队列的实例。每个请求共享资源的线程都会被封装成一个CLH队列的节点(Node)。当线程请求共享资源时,它会被添加到CLH队列的尾部,并进入阻塞状态。 当共享资源被占用时,其他线程请求该资源的线程会被放入CLH队列的末尾,即排队等待。这种排队等待的方式可以保证请求资源的线程按照FIFO的顺序获得资源,避免了饥饿现象。当资源释放后,AQS会自动唤醒队列中的下一个线程,使其获得资源并继续执行。 需要注意的是,AQS的同步队列(Sync queue)是一个双向链表,包括头节点(head)和尾节点(tail),用于后续的调度。而条件队列(Condition queue)是一个单向链表,只有在使用Condition时才会存在,并且可能会有多个条件队列。 总结一下,AQS实现阻塞队列的原理是通过CLH队列来实现的,当共享资源被占用时,请求资源的线程会被添加到CLH队列中排队等待。当资源释放后,AQS会自动唤醒队列中的下一个线程,使其获得资源并继续执行。同步队列用于后续的调度,而条件队列只在使用Condition时才会存在。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值