1.简介
ConcurrentLinkedQueue是JUC中的基于链表的无锁队列实现。本文将解读其源码实现。
2. 论文
ConcurrentLinkedQueue的实现是以Maged M. Michael和Michael L. Scott的论文Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms为原型进行改造的,不妨阅读此篇论文。
3. ConcurrentLinkedQueue的实现
3.1 数据结构
正如典型的队列设计,内部的节点用如下的Node类表示
1 | /** |
值得一提的是Node中有一个lazySetNext方法
1 | void lazySetNext(Node<E> val) { |
与AtomicReference类一样,使用了UNSAFE.putOrderedObject方法来实现低延迟的写入。这个方法会插入Store-Store内存屏障,也就是保证写操作不会重排。而不会插入普通volatile写会插入的Store-Load屏障。
ConcurrentLinkedQueue在构造时会初始化head和tail为一个item为null的节点,作为哨兵节点。
1 | private transient volatile Node<E> head; |
3.2 设计思想
ConcurrentLinkedQueue的源码还是有些晦涩难懂的,但是doc非常详细,对阅读源码非常有帮助。如果带着从doc中介绍的设计与实现思路去读源码会轻松不少。
ConcurrentLinkedQueue是不允许向其插入空的item的,对于删除元素,会将其item给CAS为null,一旦某个元素的item变为null,就意味着它不再是队列中的有效元素了,并且会将已删除节点的next指针指向自身。
这样可以实现尽可能快地从已删除的元素跳过后面删除的元素,回到队列中。
ConcurrentLinkedQueue具有以下这些性质:
- 队列中任意时刻只有最后一个元素的next为null
- head和tail不会是null(哨兵节点的设计)
- head未必是队列中第一个元素(head指向的可能是一个已经被移除的元素)
- 队列中的有效元素都可以从head通过succ方法遍历到
- tail未必是队列中最后一个元素(tail.next可以不为null)
- 队列中的最后一个元素可以从tail通过succ方法遍历到
- tail甚至可以是head的前驱
这里提到了succ方法,那么先睹为快,看一下succ方法吧。
1 | final Node<E> succ(Node<E> p) { |
因为ConcurrentLinkedQueue中的head和tail都可能会滞后,这其实是一种避免频繁CAS的优化。当然过度的滞后也是会影响操作效率的,所以在具体实现的时候,会尽可能能有机会更新head和tail就去更新它们。
3.3 源码解读
3.3.1 offer方法
1 | public boolean offer(E e) { |
3.3.2 poll方法
1 | public E poll() { |
3.3.3 peek方法
1 | public E peek() { |
3.3.4 remove方法
1 | public boolean remove(Object o) { |
3.3.5 size方法
1 | /** |