不了解SynchronousQueue?那ArrayBlockingQueue和LinkedBlockingQueue不会也不知道吧?

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情 >>

一、BlockingQueue

在我们分析线程池的时候,就涉及过阻塞队列。通过它的特性,我们可以从阻塞队列中逐一的获取需要的元素。从它的名字上就可以看得出,阻塞队列本质上依然是队列结构,即:满足先进先出的数据结构特性。同时,阻塞队列也根据调用方法后的不同响应形式,提供了多种存/取元素的方法,具体区别如下表所示:

由于使用offer方法时,如果队列已经满了,那么则无法插入成功,会立即返回false;同样的,当我们调用poll方法的时候,如果队列中是空的,则也会立即返回false;而比起立即返回的情况,我们更关注于puttake这种插入或移除失败,在当前阻塞的情况是如何实现的。之前我们曾经解析过阻塞队列SynchronousQueue——多图详解阻塞队列——SynchronousQueue,那么本篇文章我们就以ArrayBlockingQueueLinkedBlockingQueue为例,看看内部是怎么实现的。

二、ArrayBlockingQueue

在构造函数中,默认采用非公平锁方式构造阻塞队列。由构造函数入参capacity指定底层存储元素数组的长度。并且初始化了需要加锁使用的ReentrantLock实例,默认采用的是非公平锁。其中,notEmpty用于执行take时进行await()等待操作,以及put时进行signal()唤醒操作。notFull用于执行take时进行signal()唤醒操作,及put时进行await()等待操作。所以,其实就是用于标识队列是否为空或者标识队列是否满了的这两种特殊情况。相关源码及注释如下图所示:

put方法逻辑解析

在执行put方法逻辑之前,首先尝试获得可中断锁——即:lock.lockInterruptibly(),当执行interrupt操作时,该锁可以被中断。

如果数组中元素的个数(count)等于数组的长度了,也就说明队列满了,那么就在该线程上执行等待操作——notFull.await()

如果队列没有满,则调用enqueue(e)方法执行入列操作。入列操作首先会将待插入值x放入数组下标为putIndex的位置上,然后再将putIndex加1,来指向下一次插入的下标位置。此处需要注意的是,如果加1后的putIndex等于了数组的长度,那么说明已经越界了(因为putIndex是从0开始的),那么此时将putIndex置为0,即:待插入的指针指向了数组的头部——循环式插入

最后,执行count++来计算元素总个数,并且调用notEmpty.signal()方法来解除阻塞(即:当队列为空的时候,执行take方法会被notEmpty.await()阻塞)。相关源码及注释如下图所示:

take方法逻辑解析

take方法跟上面我们分析的put方法类似,区别是:出队的指针是takeIndex。如果队列中为空,那么当调用take方法执行出队操作时,就会执行notEmpty.await()方法执行等待操作,并释放锁资源。

当调用put方法向队列中放入元素之后 ,会调用notEmpty.signal()方法对等待的线程执行唤醒操作。那么线程继续执行出队操作,执行完毕后,会调用notFull.signal方法来唤醒在notFull上面await的线程。相关源码及注释如下图所示:

三、LinkedBlockingQueue

构造函数,默认长度为2^31,大概21亿多。在构造函数中,创建一个空的节点,作为整个链表的头节点。相关源码及注释如下图所示:

扫描二维码关注公众号,回复: 14430770 查看本文章

put源码和注释如下所示:

LinkedBlockingQueue的put方法和take方法逻辑都比较简单,与ArrayBlockingQueue的处理方式也很类似,区别就在于用于底层实现的用于存储元素的数据结构是不同的。这里使用的是链表结构存储的元素。 通样的,在调用put方法时,依然通过lockInterruptibly()对插入操作进行加锁操作。如果当时队列中已经满了,即:队列中存储的元素数量已经达到了Integer.MAX_VALUE,那么则执行notFull.await()进行等待,直到队列达到未满状态,执行notFull.signal()方法解除等待状态。

插入时,调用的是enqueue(...)方法,在这个方法中,将新添加的元素放入到链表的队尾,就完成了入队的操作。相关源码及注释如下图所示:

take源码和注释如下所示:

take方法与put方法也很类似,都是先调用lockInterruptibly()进行加锁操作。不同点是,这里由于是获取元素的操作,所以更在意的是队列是否为空,那么此时我们采用的就是notEmpty.await()notEmpty.signal()来控制阻塞和解除阻塞。

获取元素时,调用的是dequeue(...)方法,在这个方法中,是将head指针指向的元素指向出队操作。然后将head头指针指向原head头指针元素的next节点即可。相关源码及注释如下图所示:

今天的文章内容就这些了:

写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享

更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \(^o^)/ ~ 「干货分享,每天更新」

往期推荐

多图详解阻塞队列——SynchronousQueue

设计原则有哪些?什么是里式替换原则?

你说啥?Redis中除了五大数据类型,还有特殊数据类型!

万字长文之HashMap源码解析(包含红黑树)

图解LeetCode——11. 盛最多水的容器(难度:中等)

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

猜你喜欢

转载自juejin.im/post/7126570320378462221