阻塞队列LinkedBlockingQueue源码学习

前上一篇博客学习了ArrayBlockingQueue,今天学习LinkedBlockingQueue源码并与之对比。

LinkedBlockingQueue总结

同样先直接总结LinkedBlockingQueue相关的特性,再根据源码来进行说明,它的主要特性如下:

  1. 底层用链表实现数据存储;
  2. 是一个FIFO无界阻塞队列。队列的最大容量默认是Intenger的最大值Integer.MAX_VALUE,也可以设置自定义容量;

重要属性介绍

阻塞队列LinkedBlockingQueue源码学习
LinkedBlockingQueue有一个内部类 Node ,用来组成它存储数据的链表,Node的 item 用来存储数据, next 表示下一个节点,尾节点的next是null。
capacity 表示队列最大容量,只能在初始化的时候设置,默认是Integer.MAX_VALUE;
count 表示当前存储元素的数量,它是AtomicInteger类型;
head 链表的头节点,它的item不存放数据;
tail 链表的尾节点,它的next为null,也就是没有下一个节点;
takeLocknotEmpty 控制 take 方法的阻塞;
putLocknotFull 控制 put 方法的阻塞;

从上面属性可以猜出来LinkedBlockingQueue是用两个锁来分别控制入队和出队的操作,这是因为LinkedBlockingQueue基于链表,出队和入队操作分别在队头和队尾,理论上说并不冲突。若是像ArrayBlockingQueue一样使用独占锁,则 take 和 put 就不能真正并发。
也正是因为 take 和 put 锁分离了,所以count有可能同时存在多个线程修改,有线程安全问题所以用 AtomicInteger 。

最关键的两个私有方法enqueue和dequeue

阻塞队列LinkedBlockingQueue源码学习
LinkedBlockingQueue的 enqueuedequeue 方法比ArrayBlockingQueue的更加简单;
enqueue方法就是把新的节点放到last的next,然后把新节点设置成尾节点。
dequeue是先拿到头节点next节点作为first节点,然后头节点的next指向了自己,这里是我不太理解的地方,为什么不干脆将头节点的next设置为null。
然后first节点就作为头节点,把item拿出来后设置为null,所以头节点是不存储数据的,并且是由下一个节点升级来的。
dequeue的图示如下
阻塞队列LinkedBlockingQueue源码学习

主要方法介绍

LinkedBlockingQueue与ArrayBlockingQueue一样都是继承至 AbstractQueue并实现 BlockingQueue接口,所以他们提供的方法都差不多,功能也类似,主要实现不同,上一篇已经详细讲了,这里就只看一下takeput方法的实现。
阻塞队列LinkedBlockingQueue源码学习
阻塞队列LinkedBlockingQueue源码学习

与ArrayBlockingQueue对比

LinkedBlockingQueue与ArrayBlockingQueue是非常相似的类,通过对比能够更体现他们的优缺点,主要对比如下:

  1. 从基础属性对比LinkedBlockingQueue底层实现是链表,ArrayBlockingQueue是数组。
  2. ArrayBlockingQueue初始化需要一个数组,而LinkedBlockingQueue是动态数量的Node对象;所以ArrayBlockingQueue需要预先分配内存,而LinkedBlockingQueue不用,如果预计需要缓存的数据很多的,那么ArrayBlockingQueue一开始就需要很大一块数据。
  3. ArrayBlockingQueue添加元素更快,因为它只是要要保存的对象的引用放到数组对应位置,而LinkedBlockingQueue需要创建一个Node对象;同时在获取后这个Node对象变成了垃圾,在读写很大的情况会多出很多垃圾,可能会影响程序的性能;
  4. ArrayBlockingQueue用一个锁控制读写,LinkedBlockingQueue用两个锁分别控制读写。因为ArrayBlockingQueue为了避免数据被覆盖,而LinkedBlockingQueue不用担心这种情况,可以同时支持读写,而ArrayBlockingQueue同一时刻支持一个操作,所以LinkedBlockingQueue吞吐量更高。
  5. ArrayBlockingQueue会占用固定的内存,所以不能支持太大的缓存,但是每次操作延迟更加低,而LinkedBlockingQueue不用暂用一个初始化的内存,但是每次操作消耗的内存更大,不过同时支持读写所以吞吐量更高。