概述
ConcurrentLinkedQueue
是一个基于连接节点的无边界的线程安全队列,它采用先进先出原则对元素进行排序,插入元素放入队列尾部,出队时从队列头部返回元素,利用CAS方式实现的
ConcurrentLinkedQueue
的结构由头节点和尾节点组成的,都是使用volatile
修饰的。每一个节点由节点元素item
和指向下一个节点的next
引用组成,组成一张链表结构。
ConcurrentLinkedQueue
继承自AbstractQueue
类,实现Queue
接口
经常使用方法
boolean add(E e)
将指定元素插入此队列的尾部,当队列满时,抛出异常
boolean contains(Object o)
判断队列是否包含次元素
boolean isEmpty()
判断队列是否为空
boolean offer(E e)
将元素插入队列尾部,当队列满时返回false
E peek()
获取队列头部元素但不删除
E poll()
获取队列头部元素,并删除
boolean remove(Object o)
从队列中移指定元素
int size()
返回此队列中的元素数量,须要遍历一遍集合。判断队列是否为空时,不推荐此方法
源码分析
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
public ConcurrentLinkedQueue(Collection<? extends E> c) {
Node<E> h = null, t = null;
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (h == null)
h = t = newNode;
else {
t.lazySetNext(newNode);
t = newNode;
}
}
if (h == null)
h = t = new Node<E>(null);
head = h;
tail = t;
}
private static class Node<E> {
volatile E item;
volatile Node<E> next;
Node(E item) {
UNSAFE.putObject(this, itemOffset, item);
}
boolean casItem(E cmp, E val) {
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
void lazySetNext(Node<E> val) {
UNSAFE.putOrderedObject(this, nextOffset, val);
}
boolean casNext(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
private static final sun.misc.Unsafe UNSAFE;
private static final long itemOffset;
private static final long nextOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = Node.class;
itemOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("item"));
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}
复制代码
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
定位尾结点。
public boolean offer(E e) {
checkNotNull(e);
final Node<E> newNode = new Node<E>(e);
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
if (p.casNext(null, newNode)) {
if (p != t)
casTail(t, newNode);
return true;
}
}
else if (p == q)
p = (t != (t = tail)) ? t : head;
else
p = (p != t && t != (t = tail)) ? t : q;
}
}
复制代码
出队操做
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
if (p != h)
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
复制代码
- 出队操做是以死循环的方式直到出队成功。 第一次出队首先执行
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
节点的消耗,提供出队的效率