概述
ConcurrentLinkedQueue
是一个基于链接节点的无边界的线程安全队列,它采用先进先出原则对元素进行排序,插入元素放入队列尾部,出队时从队列头部返回元素,利用CAS方式实现的ConcurrentLinkedQueue
的结构由头节点和尾节点组成的,都是使用volatile
修饰的。每个节点由节点元素item
和指向下一个节点的next
引用组成,组成一张链表结构。ConcurrentLinkedQueue
继承自AbstractQueue
类,实现Queue
接口
常用方法
boolean add(E e)
将指定元素插入此队列的尾部,当队列满时,抛出异常boolean contains(Object o)
判断队列是否包含次元素
boolean isEmpty()
判断队列是否为空boolean offer(E e)
将元素插入队列尾部,当队列满时返回falseE peek()
获取队列头部元素但不删除E poll()
获取队列头部元素,并删除boolean remove(Object o)
从队列中移指定元素
int size()
返回此队列中的元素数量,需要遍历一遍集合。判断队列是否为空时,不推荐此方法
源码分析
1 | // 头节点 |
offer入队操作
初始化
- 初始化操作就是创建一个新结点,并且
head
和tail
相等,结点的数据域为空。 - 当第一次入队操作时,检查插入的值是否为空,为空则抛空指针,然后用当前的值创建一个新
Node
结点。然后执行死循环开始入队操作 - 首先定义了两个指针
p和t
,都指向tail
- 然后定义
q
结点存储p
的next指向的结点,此时p的next是为空没有结点的 - 此时
q==null
条件成立。执行p.casNext(null, newNode)
.以cas方式把p的下一个节点指向新创建出来的结点,然后往下执行,p=t
直接返回true。此时初始化构造的结点的next指向第一次入队成功的结点 - 第二次入队操作,首先也是非空检查,然后创建一个新结点。此时死循环入队操作。定义了两个指针
p和t
,都指向tail
。定义q
结点存储p
的next指向的结点,此时p的next是不为空的,指向了上面创建的结点。所以q==null
不成立。执行else操作
- 此时
p
也不等于q
。p!=t
不成立,p和t都是指向tail
。因为不成立所以让p=q
,此时p和q都是指向第二个结点。再次循循环操作。 - 然后再次
p和t
,都指向tail
。定义q
结点存储p
的next指向的结点。此时p的next指向还是空,所以q=null
成立。执行p.casNext(null, newNode)
.以cas方式把p
的next
指向新创建出来的结点。 - 此时
if (p != t)
是成立的 执行casTail(t, newNode);
期望值是t
,更新值新创建的结点。于是更新了tail结点移动到最后添加的结点
大概的入队流程就是这样重复上述操作。直到入队成功。tail
结点并不是每次都是尾结点。所以每次入队都要通过tail
定位尾结点。
1 | public boolean offer(E e) { |
出队操作
1 | public E poll() { |
- 出队操作是以死循环的方式直到出队成功。 第一次出队首先执行
for (Node<E> h = head, p = h, q;;)
定义两个指针p
和h
都指向head
- 然后定义一个item存储p(这里就是head)的值,然后判断item是否为空,此时第一次出队时为空的,则执行
else if ((q = p.next) == null)
,此条件不成立,因为head的next有结点。执行else if (p == q)
,此时不相等,因为上个操作已经把q赋值为p的next结点了
。所以执行最后的else语句p = q;
在次循环执行。 - 此时
p.item
不为空条件成立且以cas
方式更新p
的item
为空p.casItem(item, null)
。如果都两个条件都成立,判断if (p != h)
此时不成立的,更新updateHead(h, ((q = p.next) != null) ? q : p);
预期值是h,
更新值是q
因为不为空。并返回item,第一次出队成功。
总结
CoucurrentLinkedQueue
的结构由头节点和尾节点组成的,都是使用volatile
修饰的。每个节点由节点元素item
和指向下一个节点的next
引用组成.入队
:先检查插入的值是否为空,如果是空则抛出异常。然后以死循坏的方式执行一直到入队成功,整个过程大概就是把tail
结点的next
指向新结点,然后更新tail
为新结点即可。但是tail
结点并不是每次都是尾结点。所以每次入队都要通过tail
定位尾结点。出队
:出队操作就是从队列里返回一个最早插入的节点元素,并清空该节点对元素的引用。并不是每次出队都更新head
节点,当head
节点有元素时,直接弹出head
节点的元素,并以cas
方式设置节点的item
为null
,不会更新head
节点。只有当head
节点没有元素值时,出队操作才会更新head
节点,这种做法是为了减少cas
方式更新head
节点的消耗,提供出队的效率