Java队列与线程安全:深入理解阻塞队列
本篇文章使用的代码:java
前言:在数据结构的时候,我们学过队列这一结构,这里我们进行简单的回顾一下,队列就像它的名字一样,我们生活中参加活动人多的时候,或者打饭的时候经常会排队,那排队的特点是什么呢? 先到先得。先进行排队的人会先参加活动活或打到饭。这也是我们队列的特点:先进先出。先入队的元素先出队,后入队的元素后入队。,下面,我们将结合线程安全对队列进行更深层面的讨论。
1.阻塞队列的概念
在讲解阻塞队列的概念之前,我们先从它的应用场景入手。所谓的生产者消费者模型,举个栗子:我是一个煎饼果子摊贩,我在我的摊位上做煎饼果子,那我就是煎饼果子的生产者,来我的摊买煎饼果子的顾客就是消费者,我做完一个煎饼果子之后,就要拿给我的消费者,做完下一个煎饼果子之后,就要拿给我的下一个消费者……我提供煎饼果子,顾客吃煎饼果子,这就是生产者消费者模型。
那为什么说生产者消费者模型是阻塞队列的主要应用场景呢?很简单,当顾客比较多的时候,我是不是得把做好的煎饼果子拿给最先来的顾客呀,这和我们上述所提到的,队列很相似——先到先得。因此队列可以很好的描述生产者消费者模型。

再讲一个极端的情况:当我做煎饼果子的速度和顾客的人数不平衡的时候,例如我做煎饼果子的速度比较快,但是却没有那么多的客户,我是不是要把多的煎饼果子放在我的摊面上(这里不考虑煎饼果子会变冷的情况~)
对应过来,当生产者和消费者之间的平衡被打破的时候,就需要出现一个平台来维护生产者与消费者之间的平衡,那这个平台其实就是阻塞队列。
通常,我们有两个服务器,服务器A进行请求的作用,而服务器B则进行处理A的请求并回应A的作用,当A的访问量过大,也会对B造成很大的压力,因为B的主要工作是回应,因此B的内存不会太大,这样会对B造成很严重的破坏,这就需要一个阻塞队列,阻塞队列负责存放A过多的请求,并且降低把请求给B的速度,这样就能减少B的压力,也能让服务器B更安心的进行自己的工作,这就是阻塞队列充当的角色。
2.java中的阻塞队列
java为我们提供了阻塞队列的使用,我们用ArrayBlockingQueue进行演示
ArrayBlockingQueue<String> arrayBlockingQueue=new ArrayBlockingQueue<String>(1000);
//<>是泛型,就是队列存储元素的类型,注意这里要写类如整型要写Integer,而不是写int……
//()里面是阻塞队列的数组空间大小
入队操作:当队列满时,想要入队,就会进行阻塞,等到队列不满的时候阻塞等待才会结束

注意到put下方有红线,是因为使用put得抛出异常InterruptedException,鼠标滑到put,按住Alt+enter

就可以自动抛出异常
同样:出队操作:当队列为空的时候,想要出队,就会进行阻塞等待,当其他线程进行入队操作之后,才会通知该线程,阻塞才能结束,和入队一样,涉及到线程安全问题,需要抛出异常InterruptedException

阻塞队列没有提供首元素的方法。
3.手把手教你实现阻塞队列
首先要创建数组用来存储元素,定义三个变量分别存放首下标,最后一个元素的下标和数组的元素真实个数

//这里用字符串数组做示例
构造方法来为array数组开辟空间

阻塞队列和队列一样是先进先出的,对应成数组,入队就是尾插法,出队是头删法
先是尾插法,很简单,只要把要插入的元素放在array[tail]就可以了,但是我们要考虑队列是否满了,如果满了,那我们就不能插入了。

重点:尾插之后还要再一次判断tail是否超过size,如果超过,tail就不能++;而是把tail=赋值为0。相当于队列已经满了下一次尾插时才不会数组越界。

头删法:首先要判断数组是否为空,如果数组为空,就没有元素可以删除,无法删除

判断完之后,需要创建一个String变量来存放head下标的元素,head++,size--

同样,当head++完之后,需要判断head是否超过size,如果是,head=0;相当于队列已经被删空了

以上的代码实现当涉及到多线程会引发线程安全问题,所以我们需要给这些操作上锁,


上完锁之后,回到最开始的判断条件,我们来解决当不符合入队,出队的条件的时候,我们要怎么办,很简单,当还没有满足入队和出队的条件的时候,那我们就让线程等待到可以入队出队不就好了吗,我们在线程安全问题中学到wait和notify就可以用上,在不符合条件的时候wait等待,当一个线程操作完之后在notify通知阻塞等待的线程让该线程作用。


最后再来思考极端情况:假设现在有三个线程同时在等待要入队,当有一个线程删除一个元素之后,就会随机通知一个在等待的线程可以执行了,三个线程当中一个线程去入队完之后,是否有可能会通知剩下两个线程呢,答案是有可能的,为了杜绝其他线程认为可以入队了,我们要把最开始的if改成while,在通知完之后再次判断是否真的可以进行入队,如果是,那就入队,如果不是,那就再次等待。出队操作同样如此。


最后我们写两个线程来验证我们的代码是否正确



这样我们的代码就算完成了。
4.阻塞队列的优势与劣势
优势:
1.解耦合
服务器之间不会出现和对方相关的代码,只会出现和阻塞队列相关的代码,可以减少服务器之间的关系,当服务器需要修改时,减少麻烦。
2.削峰填谷
当服务器A请求量过大时,可以保护服务器B,减少B消耗的资源。当这波过去时,B也可以保持原来的速度来处理并回应这些请求
缺点:
1.结构变得复杂
2.执行效率变低
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)