一、简介:
工作中有时
候需要使用
线
程安全的
队
列。如果要
实现
一个
线
程安全的
队
列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队
列可以用一个
锁 (入队
和出
队
用同一把
锁
)或两个
锁
(入
队
和出
队
用不同的
锁
)等方式来
实现
。非阻塞的
实现
方式则
可以使用循
环
CAS
的方式来
实现
。
而ConcurrentLinkedQueue 就是juc包中自带的经典非堵塞方式实现的工具类
二、结构
ConcurrentLinkedQueue
由
head
节
点和
tail
节
点
组
成,每个
节
点(
Node
)由
节
点元素(
item
)和
指向下一个
节
点(
next
)的引用
组
成,
节
点与
节
点之
间
就是通
过这
个
next
关
联
起来,从而
组
成一
张链
表
结
构的
队
列。默
认
情况下
head
节
点存
储
的元素
为
空,
tail
节
点等于
head
节
点。
private transient volatile Node<E> tail = head;
三、入队
从源代
码
角度来看,整个入
队过
程主要做两件事情:第一是定位出尾
节
点;第二是使用
CAS
算法将入
队节
点
设
置成尾
节
点的
next
节
点,如不成功
则
重
试
。
public boolean offer(E e) {
checkNotNull(e);
// 入队前,创建一个入队节点
final Node<E> newNode = new Node<E>(e);
for (Node<E> t = tail, p = t;;) {
// 创建一个指向tail节点的引用
Node<E> q = p.next;
if (q == null) {
// p is last node
if (p.casNext(null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
tail
节
点并不
总
是尾
节
点,所以每次入
队
都必
须
先通
过
tail
节
点来找到尾
节
点。尾
节
点可能
是
tail
节
点,也可能是
tail
节
点的
next
节
点。代
码
中循
环
体中的第一个
if
就是判断
tail
是否有
next
节
点,有
则
表示
next
节
点可能是尾
节
点。
获
取
tail
节
点的
next
节
点需要注意的是
p
节
点等于
p
的
next
节
点的情况,只有一种可能就是
p
节
点和
p
的
next
节
点都等于空,表示
这
个
队
列
刚
初始化,正准
备
添加
节
点,所以需要返回
head
节
点。
/**
*返回 p 的后继节点,或者如果 p.next 已经链接到 self 则返回头节点,这只有在使用现在不在列表*中的陈旧指针遍历时才会为真。
**/
final Node<E> succ(Node<E> p) {
Node<E> next = p.next;
return (p == next) ? head : next;
}
四、出列
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
//入列折腾的tail,那出列折腾的就是head
E item = p.item;
//出列判断依据是节点的item=null
//item != null, 并且能将操作节点的item设置null, 表示出列成功
if (item != null && p.casItem(item, null)) {
if (p != h)
//一旦出列成功需要对head进行移动
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
//推动head节点移动
p = q;
}
}
}
五、ConcurrentLinkedQueue使用特点
ConcurrentLinkedQueue使用约定:
1:不允许null入列
2:在入队的最后一个元素的next为null
3:队列中所有未删除的节点的item都不能为null且都能从head节点遍历到
4:删除节点是将item设置为null, 队列迭代时跳过item为null节点
5:head节点跟tail不一定指向头节点或尾节点,可能存在滞后性