转载请标注来源:https://www.cnblogs.com/xmzJava/p/9380649.htmlhtml
在分析多线程的文章中,咱们知道了Executors是经过阻塞队列接受任务。例如 FixedThreadPool 使用的是 LinkedBlockingQueue, CachedThreadPool 使用的是 SynchronousQueue。阻塞队列的基类是 BlockingQueue,他的实现类以下所示api
BlockingQueue的api咱们须要重点关注下,理解了这些api的做用,对于实现类的分析会轻松不少。数组
类型 | api名称 | 是否阻塞 | 简述 |
放入数据 | offer(anObject) | 否 | 将anObject加到BlockingQueue里,即若是BlockingQueue能够容纳,则返回true,不然返回false |
offer(E o, long timeout, TimeUnit unit) | 否 | 能够设定等待的时间,若是在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败 | |
put(anObject) | 是 | 把anObject加到BlockingQueue里,若是BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续 | |
获取数据 | poll(time) | 否 | 取走BlockingQueue里排在首位的对象,若不能当即取出,则能够等time参数规定的时间,取不到时返回null |
poll(long timeout, TimeUnit unit) | 否 | 同上 | |
take() | 是 | 取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入 | |
drainTo() | 否 | 一次性从BlockingQueue获取全部可用的数据对象(还能够指定获取数据的个数),经过该方法,能够提高获取数据效率;不须要屡次分批加锁或释放锁 |
重点关注表中的阻塞类型的方法,他们是阻塞队列的核心。接下来说述几个经常使用的阻塞队列的存取数据api。缓存
在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象。咱们先看offer方法数据结构
咱们再看下 enqueue多线程
以上是非阻塞的存放。咱们再看阻塞版本的存放spa
能够看到,两种方式的差异其实很小,一个是阻塞一段时间后直接返回false,一个是无限期的阻塞。咱们再看下取数据的api。.net
再看下dequeue线程
以上就是ArrayBlockingQunue的具体分析。debug
LinkedBlockingQueue是基于链表的阻塞队列,首先须要注意的是LinkedBlockingQueue是能够无界的,当你不指定容量时他默认的大小是 Integer.MAX_VALUE
链表经过内部的Node来实现,能够看出这是个单项链表。
接下来,咱们再看下具体的api
咱们看下 enqueue,就是一个简单的链表操做。
put的操做和offer类似,这里就省略了,再看下poll
看下dequeue,把头结点取出来,把下个节点设为头结点
以上就是LinkedBlockingQueue。
经过上述两种队列的讲解,咱们大概知道了队列存取元素的大体过程,其余队列和上述两种队列的api大体相同,因此接下重点讲述队列的大体特色,再也不对api进行详细的描述。
PriorityQueue并非阻塞队列,在这里讲述是由于,接下来的几个队列都是基于他的扩展。PriorityQueue和ArrayBlockingQueue同样,内部维护了一个定长数组,若是不指定长度,默认长度就是11。PriorityQueue是一个优先级队列,元素的顺序并非按照插入顺序而来,而是按照元素的大小来判断。默认的比较器是从小到大,即队首的元素老是最小的。固然。能够自定义比较器。
PriorityQueue的优先级经过二叉小顶堆实现,他的逻辑结构是一棵彻底二叉树,存储结构实际上是一个数组。逻辑结构层次遍历的结果恰好是一个数组。这里借鉴网上的一幅图
咱们看下添加元素的过程,这里重点是siftUp方法
由上图能够看出,PriorityQunue 最关键的即是 比较-交换 步骤。 添加元素都是先放到最后,而后再与本身的父节点比较
再看下获取元素的步骤
这里还有种状况,就是方法,删除中间的某个元素,这就是上述两种变化的结合,首先删除 i 下标的元素,而后把末尾的元素放置到 i 坐标,先向下比较看看,再向上比较。具体的代码这里就省略了。
以上就是PriorityQueue的具体分析。
DelayQueue 是延迟阻塞队列,队列中的元素只有当其指定的延迟时间到了,才可以从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,所以往队列中插入数据的操做(生产者)永远不会被阻塞,而只有获取数据的操做(消费者)才会被阻塞。
DelayQueue内部经过PriorityQueue实现延迟的权重排序 ,其比较器是 Comparable<Delayed>
主要看存元素的api
这里关键的是
first.getDelay(NANOSECONDS)>0
因此咱们在实现 Delayed的时候 实现类里面必定得要有时间能够记录到什么过时,若是过时了必定要返回负数
DelayQueue平时比较少见,可是咱们能够用它作一些很灵活的事情,例如缓存过时,空闲链接去除。按照DelayQueue的特性,队首的元素老是最早过时的,咱们能够用一个后台线程监听DelayQueue的队首元素。
这里 你们能够参考下用 DelayQueue实现一个过时缓存清除的功能。
PriorityBlockingQueue是优先级阻塞队列,他和咱们上文讲的PriorityQueue很是类似。可是呢他又多了点阻塞的东西,准确来讲是多了半点。由于在作put操做的时候是不会有阻塞的
就算咱们给他一个初始大小,可是若是容量不够还会去扩容
因此咱们在往里面put的时候要注意,万一辈子产者一直在生产,消费者挂了,那么内存很容易就会被耗尽
在线程池里面,你们必定见到过这个队列。这是一个不直接存放元素的队列,他存储的其实是他内置的Node。它生产产品(即put的时候),若是当前没有人想要消费产品(即当前没有线程执行take),今生产线程必须阻塞,等待一个消费线程调用take操做,take操做将会唤醒该生产线程,同时消费线程会获取生产线程的产品。具体的流程我举个例子
步骤一,调用put线程,存储元素
步骤二,再次调用put线程,存储元素 ,在调用一个take线程,取出一个元素
这个时候take线程会和put("b")配对,配对成功后put("a")线程就会成为头结点,整个流程以下图所示
SynchronousQueue有两种模式,公平和非公平,默认是非公平的。内部有两种数据结构,分别是是队列和栈来表示公平和非公平两种模式。上图所描述的是非公平模式,即先进后出。具体细节仍是得从代码里看。
首先看下存取元素的api
两个方法都调用的 transfer 方法,这就是一个配对的方法。这里截取核心部分的transfer方法供你们参考
这里采用了大量的CAS操做进行更新,初看有点乱,可是心中熟记这是个配对方法,再debug几回。代码就会很清晰了。
参考:
https://blog.csdn.net/u013309870/article/details/71189189 【Java堆结构PriorityQueue彻底解析】
http://www.cnblogs.com/leesf456/p/5560362.html 【SynchronousQueue分析】