上一篇咱们已经学习过了 ArrayBlockingQueue的知识及相关方法的使用,这一篇咱们就来再学习一下ArrayBlockingQueue的亲戚 LinkedBlockingQueue。在集合类中 ArrayList与 LinkedList会经常拿来比较,ArrayList内部实现是基于数组的,而 LinkedList内部实现是基于链表,因此他们之间会有不少不一样,可是本文不会去重点讨论,感兴趣的朋友能够参考我以前发过的几篇文章,那么有请本节的主角 LinkedBlockingQueue!node
LinkedBlockingQueue数组
LinkedBlockingQueue是一个一个基于已连接节点的、范围任意(相对而论)的 blocking queue。此队列按 FIFO(先进先出)排序元素。队列的头部 是在队列中时间最长的元素。队列的尾部 是在队列中时间最短的元素。新元素插入到队列的尾部,而且队列获取操做会得到位于队列头部的元素。连接队列的吞吐量一般要高于基于数组的队列,可是在大多数并发应用程序中,其可预知的性能要低。 并发
可选的容量范围构造方法参数做为防止队列过分扩展的一种方法。若是未指定容量,则它等于 Integer.MAX_VALUE。除非插入节点会使队列超出容量,不然每次插入后会动态地建立连接节点。 性能
LinkedBlockingQueue及其迭代器实现了 Collection 和 Iterator 接口的全部可选 方法。 学习
咱们已经学习过了 ArrayBlockingQueue,因此学习 LinkedBlockingQueue就天然比较轻松,因此本文对于已经明确的相关概念就不作过多介绍了,而是重点放在二者的区别之上。this
1.成员变量spa
与ArrayBlockingQueue不一样 LinkedBlockingQueue的成员变量有些变化,如下是 LinkedBlockingQueue的成员变量:线程
1)首先 LinkedBlockingQueue明确了容量变量,当为指定容量时,默认容量为Int的最大值Integer.MAX_VALUE。对象
2)队列元素数量变量 count采用的是 AtomicInteger ,而不是普通的Int型。CAS相关可参考http://286.iteye.com/blog/2295165blog
3)LinkedBlockingQueue内部队列实现使用的是 Node节点类,这与 LinkedList相似。
4)最后也是最重要的一点,那就是获取与插入操做分红了两个锁:takeLock与 putLock来处理,这点下面还会重点分析。
2.构造方法
有三个构造方法,分别为默认,指定容量,指定容量和初始元素。
默认构造方法建立一个容量为 Integer.MAX_VALUE的 LinkedBlockingQueue实例。
第二种构造方法,指定了队列容量,首先判断指定容量是否大于零,不然抛出异常。而后为 capacity 赋值,最后建立空节点,并指向 head与 last,二者的 item与 next此时均为 null。
最后一种,利用循环向队列中添加指定集合中的元素。
3.Node类
LinkedBlockingQueue内部列表实现是使用的 Node内部类,Node类也并不复杂,如下是其源代码:
item用于表示元素对象,next指向链表的下一个节点。
LinkedBlockingQueue的大部分方法实际上是与 ArrayBlockingQueue相似的,因此本文就只介绍不一样于ArrayBlockingQueue的相关方法。
4.添加元素
1)add方法
add方法相同就不介绍了,一样调用的是offer方法。
2)offer方法
将指定元素插入到此队列的尾部(若是当即可行且不会超出此队列的容量),在成功时返回 true,若是此队列已满,则返回 false。当使用有容量限制的队列时,此方法一般要优于 add 方法,后者可能没法插入元素,而只是抛出一个异常。
与ArrayBlockingQueue不一样,LinkedBlockingQueue多了一些容量方面的判断。
能够看到offer方法的关键在于 insert方法。
3)insert方法
insert方法很是简单,可是却不要小看。
首先,根据指定参数x建立一个Node实例。
而后,将原尾节点的next指向此节点。
最后,将尾节点设置尾此节点。
这样新添加的节点就成为了新的尾节点。
当向链表中添加第一个节点时,由于在初始化时
因此此时 head与 last指向的是同一个对象new Node<E>(null)。
以后将last.next指向x。
由于此时 head与 last是同一个对象,因此 head.next也指向x。
最后将 last指向x。
这样 head的next就指向了 last。此时head中的 item仍为 null。
4)put方法
将指定元素插入到此队列的尾部,若有必要,则等待空间变得可用。
5.获取元素
1)peek方法
peek方法获取但不移除此队列的头;若是此队列为空,则返回 null。
peek方法从头节点直接就能够获取到第一个添加的元素,因此效率是比较高的。若是不存在则返回null。
2)poll方法
poll方法获取并移除此队列的头,若是此队列为空,则返回 null。
poll与 peek方法不一样在于poll获取完元素后移除这个元素,获取与移除是经过 extract()方法实现的。
注意:其中须要注意的是最后部分代码:
确定会有朋友有如下疑问:
1)队列都已经满了,还须要唤醒添加线程干什么?
2)线程满了就不该该再向里面添加元素了啊?
3)signalNotFull方法是干什么的?
signalNotFull方法的做用是唤醒等待中的put线程,signalNotFull只能被 take/poll方法调用,如下是 signalNotFull方法的源代码:
前两点问题其实转换一下角度就能很好的理解了,虽然队列已经满了,可是此时本线程已经完成了添加,可是其余线程还在等待获取条件进行添加,若是不去主动唤醒的话,那么这些添加操做就只能无限期的等待下去,因此这些等待的添加操做就会失效。因此此时须要唤醒已经排队的添加线程,虽然他们已经没法添加元素至队列。
3)extract方法
extract方法用于获取并移除头节点。
这里须要注意的是这里指的头节点并非 head,而是 head的 next所指 Node的 item元素。由于 head的 item永远为 null。last的 next永远为 null。
4)take方法
获取并移除此队列的头部,在元素变得可用以前一直等待(若是有必要)。
与 poll方法相似,只是take方法采用阻塞的方式来获取元素。
7.其余方法
1)remainingCapacity方法
也就是返回能够当即添加元素的数量。
2)iterator方法
iterator方法返回在队列中的元素上按适当顺序进行迭代的迭代器。返回的 Iterator 是一个“弱一致”的迭代器,从不抛出 ConcurrentModificationException,而且确保可遍历迭代器构造后所存在的全部元素,而且可能(但并不保证)反映构造后的全部修改。
iterator方法返回的是一个Itr内部类的实例,经过这个实例能够遍历整个队列。如下是Itr内部类的源代码:
Itr类不复杂,我就不详细解释了。
3)清除方法
clear,drainTo等方法与 ArrayBlockingQueue相似,这里就不说了。
8,.LinkedBlockingQueue与 ArrayBlockingQueue
1)内部实现不一样
ArrayBlockingQueue内部队列存储使用的是数组:
而 LinkedBlockingQueue内部队列存储使用的是Node节点内部类:
2)队列中锁的实现不一样
从源代码就能够看出 ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加与获取使用的是同一个锁;而 LinkedBlockingQueue实现的队列中的锁是分离的,即添加用的是 putLock,获取是 takeLock。
3)初始化条件不一样
ArrayBlockingQueue实现的队列中必须指定队列的大小。
LinkedBlockingQueue实现的队列中能够不指定队列的大小,默认容量为Integer.MAX_VALUE。
4)操做不一样
ArrayBlockingQueue不管是添加仍是获取使用的是同一个锁,因此添加的同时就不能读取,读取的同时就不能添加,因此锁方面性能不如 LinkedBlockingQueue。
LinkedBlockingQueue读取与添加操做使用不一样的锁,由于其内部实现的特殊性,添加的时候只须要修改 last便可,而不会影响 head节点。而获取时也只须要修改 head节点便可,一样不会影响 last节点。因此在添加获取方面理论上性能会高于 ArrayBlockingQueue。
因此 LinkedBlockingQueue更适合实现生产者-消费者队列。