继承关系
对比ConcurrentLinkedQueue在继承关系上,ConcurrentLinkedDeque多实现了一个Deque接口。Deque是双端队列的接口,在行为上扩充了Queue接口,可以在队列头与对列尾进行操作。
重要字段
// 链表头结点
private transient volatile Node<E> head;
// 链表尾结点
private transient volatile Node<E> tail;
private static final Node<Object> PREV_TERMINATOR, NEXT_TERMINATOR;
ConcurrentLinkedDeque中存在头尾结点,还有两个中止结点:
- PREV_TERMINATOR:prev的终止节点,next指向自身,即
PREV_TERMINATOR.next = PREV_TERMINATOR
。在 first 节点出列后,会把first.next指向自身(first.next=first
),然后把prev设为PREV_TERMINATOR
。 - NEXT_TERMINATOR:next的终止节点,prev指向自身,即
NEXT_TERMINATOR.pre = NEXT_TERMINATOR
。在 last 节点出列后,会把last.prev指向自身(last.prev=last
),然后把next设为NEXT_TERMINATOR
。
结点的类型是Node内部类,也是比较好理解的:
static final class Node<E> {
volatile Node<E> prev;
volatile E item;
volatile Node<E> next;
Node() { // default constructor for NEXT_TERMINATOR, PREV_TERMINATOR
}
/**
* Constructs a new node. Uses relaxed write because item can
* only be seen after publication via casNext or casPrev.
*/
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);
}
void lazySetPrev(Node<E> val) {
UNSAFE.putOrderedObject(this, prevOffset, val);
}
boolean casPrev(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, prevOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long prevOffset;
private static final long itemOffset;
private static final long nextOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = Node.class;
prevOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("prev"));
itemOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("item"));
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}
构造函数
// 空构造器
public ConcurrentLinkedDeque() {
head = tail = new Node<E>(null); // 头部和尾部都为空节点
}
// 通过集合,构造队列
public ConcurrentLinkedDeque(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);
newNode.lazySetPrev(t);
t = newNode;
}
}
initHeadTail(h, t); // 最后设置头部和尾部节点
}
总共两个构造器,第一个是无参构造器,内部初始化头尾结点,第二个构造器接受外部传递集合对象,其内部将数据组织为链表。
重要方法
入列
ConcurrentLinkedDeque的入队方法有很多,例如:addFirst(e)、addLast(e)、offerFirst(e)、offerLast(e)
public void addFirst(E e) { linkFirst(e); } // 添加到首位
public void addLast(E e) { linkLast(e); } //添加到末尾
public boolean offerFirst(E e) {
linkFirst(e);
return true;
}
public boolean offerLast(E e) {
linkLast(e);
return true;
}
可以看到其内部调用link~方法,这里以linkFirst为例子:
private void linkFirst(E e) {
checkNotNull(e); // 空检查
final Node<E> newNode = new Node<E>(e); //创建新节点
restartFromHead:
for (;;)
// 前驱节点 != null && 前驱的前驱节点 != null
for (Node<E> h = head, p = h, q;;) {
if ((q = p.prev) != null && (q = (p = q).prev) != null)
// 说明head被修改,返回head重新查找
p = (h != (h = head)) ? h : q;
// 自连接节点,不能从p开始查找,退出重新循环
else if (p.next == p)
continue restartFromHead;
else {
// p 是第一个node,则更新新节点next指向p
// 尝试更新p的前驱指向新节点,更新失败则重新循环更新
newNode.lazySetNext(p); // CAS piggyback
if (p.casPrev(null, newNode)) {
//新节点入队成功
if (p != h) // hop two nodes at a time
casHead(h, newNode); //将新节点设置为头节点
return;
}
// 执行到此处说明CAS操作失败,有其它线程也在队首插入元素
}
}
}
出列
ConcurrentLinkedDeque的出队一样分为队首、队尾两种情况:removeFirst()、pollFirst()、removeLast()、pollLast()。
public E removeFirst() { return screenNullResult(pollFirst()); }
public E removeLast() { return screenNullResult(pollLast()); }// 移除第一个节点
public E pollFirst() { // first() 找到第一个节点, succ()返回下一个节点
for (Node<E> p = first(); p != null; p = succ(p)) {
E item = p.item; // 节点数据
if (item != null && p.casItem(item, null)) { // CAS 将当前节点数据清空
unlink(p); //取消当前节点连接
return item; //返回节点数据
}
}
return null;
}// 移除最后一个节点
public E pollLast() { // last() 找到最后一个节点, pred() 返回上一节点
for (Node<E> p = last(); p != null; p = pred(p)) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
unlink(p);
return item;
}
}
return null;
}
上面涉及到的方法first()、last()、succ()、pred()
// 返回首节点
Node<E> first() {
restartFromHead:
for (;;) // 从head开始往前找
for (Node<E> h = head, p = h, q;;) {
if ((q = p.prev) != null && (q = (p = q).prev) != null) // 如果head被修改则返回新的head重新查找,否则继续往前(pred)查找
p = (h != (h = head)) ? h : q;
else if (p == h || casHead(h, p)) // 找到的节点不是head节点,CAS修改head
return p;
else
continue restartFromHead;
}
}
// 返回尾结点
Node<E> last() {
restartFromTail:
for (;;)
for (Node<E> t = tail, p = t, q;;) {
if ((q = p.next) != null && (q = (p = q).next) != null)
p = (t != (t = tail)) ? t : q;
else if (p == t || casTail(t, p))
return p;
else
continue restartFromTail;
}
}
// 返回后继节点
final Node<E> succ(Node<E> p) {
Node<E> q = p.next;
return (p == q) ? first() : q;
}
// 返回前驱节点
final Node<E> pred(Node<E> p) {
Node<E> q = p.prev;
return (p == q) ? last() : q;
}
CAS 修改节点的 item 为 null(即 “逻辑删除-logical deletion”),然后调用unlink§方法解除节点链接,最后返回 item。unlink§是移除节点的主方法
void unlink(Node<E> x) {
final Node<E> prev = x.prev;
final Node<E> next = x.next;
if (prev == null) { // 前驱为空,表示是第一个节点
unlinkFirst(x, next);
} else if (next == null) { // 后继为空,表示是最后一个节点
unlinkLast(x, prev);
} else { // 中间节点
Node<E> activePred, activeSucc;
boolean isFirst, isLast;
int hops = 1;
// 从被删除节点往前找到第一个有效前驱节点
for (Node<E> p = prev; ; ++hops) {
if (p.item != null) { // 找到有效节点
activePred = p;
isFirst = false;
break;
}
Node<E> q = p.prev;
if (q == null) { // 已经到了头部了
if (p.next == p) // 发现自链接,直接返回
return;
activePred = p;
isFirst = true;
break;
}
else if (p == q) //同样是自链接
return;
else // 更新循环指针
p = q;
}
// 从被删除节点往后找到第一个有效后继节点
for (Node<E> p = next; ; ++hops) {
if (p.item != null) { // 找到有效节点
activeSucc = p;
isLast = false;
break;
}
Node<E> q = p.next;
if (q == null) { // 已经到队尾了
if (p.prev == p) // 发现自链接,直接返回
return;
activeSucc = p;
isLast = true;
break;
}
else if (p == q) //自链接
return;
else
p = q; // 更新循环指针
} // 如果已经积累了超过临界值的逻辑删除节点,或者是内部节点删除,我们需要进一步处理unlink / gc-unlink
if (hops < HOPS && (isFirst | isLast))
return;
// 移除有效前驱和后继节点之间的那些节点(都是逻辑删除的节点),包括x节点本身,就是使 有效前驱 和 后继节点相连
skipDeletedSuccessors(activePred);
skipDeletedPredecessors(activeSucc); // 如果更新的开头或者结尾,那么就可以尝试进行gc-unlink
if ((isFirst | isLast) && // 确保前驱和后继的状态没有被改变 (activePred.next == activeSucc) && (activeSucc.prev == activePred) && (isFirst ? activePred.prev == null : activePred.item != null) && (isLast ? activeSucc.next == null : activeSucc.item != null)) { // 确保x节点不能从head/tail节点被访问
updateHead();
updateTail();
x.lazySetPrev(isFirst ? prevTerminator() : x); // 前驱终结节点
x.lazySetNext(isLast ? nextTerminator() : x); // 后继终结节点
}
}
}
// 从first开始往后找到第一个有效节点,直到找到或者到达队列的最后一个节点为止,并把first的直接后继指向该有效节点:
// 1) 如果first的后继本身就是有效节点,不做任何处理
// 2) 否则往后依次找到第一个有效节点,并把first的后继指向该有效节点
private void unlinkFirst(Node<E> first, Node<E> next) {
for (Node<E> o = null, p = next, q;;) { // p是有效节点 || p是最后一个节点
if (p.item != null || (q = p.next) == null) { // 第一次循环,o为null。从第二次循环开始,p是o的后继有效节点,并且p还没有断开o,将first后继指向该有效节点p
if (o != null && p.prev != p && first.casNext(next, p)) {
skipDeletedPredecessors(p); // 确保first还是第一个节点,没有被其他线程改变状态,并且它和它的后继节点p是直接相连接的,这种关系没有被破坏
if (first.prev == null && (p.next == null || p.item != null) && p.prev == first) {
updateHead(); // Ensure o is not reachable from head
updateTail(); // Ensure o is not reachable from tail
o.lazySetNext(o);
o.lazySetPrev(prevTerminator());
}
}
return;
}
else if (p == q)
return;
else {
o = p;
p = q;
}
}
}
这里以pollFirst出队方法为例,其他方法逻辑都一样。其逻辑如下:
(1)先通过first()拿到队列头部的第一个节点,如果是活动节点(item不为null),则直接将item置为null,即完成了删除节点的第一步逻辑删除,
(2)执行unlink方法执行删除节点
(3)GC-unlinking,unlink方法针对节点在不同的位置按不同的逻辑处理
1)如果出队的节点是队列的第一个节点,则执行unlinkFirst;
2)如果是队列的最后一个节点,则执行unlinkLast,③否则表示是内部节点,执行unlink本身的通用节点逻辑。
其中unlinkFirst的逻辑其实就分两个部分:
第一部分是实现从被移除节点p开始往后(队尾)找到第一个有效节点,直到找到或者到达队列的最后一个节点为止,并把p的直接后继指向该有效节点(如果本身不是其后继节点的话),其中的skipDeletedPredecessors方法实现将刚刚找到的后继节点的前驱也指向节点p,即完成它们的互联,这一步就是所谓的unlinking,使队列的活动节点无法访问被删除的节点;
第二部分是通过updateHead、updateTail使被删除的节点无法从head/tail可达,最后让被删除节点后继自连接,前驱指向前向终结节点。如果是内部节点出队,执行unlink本身,先找到被删除节点x的有效前驱和后继节点,并记录它们中间的已经被逻辑删除的节点个数,如果已经积累了超过阈值的节点个数,或者是内部节点删除,我们需要进一步处理unlink/gc-unlink
1)首先使被删除节点的有效前驱节点和后继节点互联,就相当于导致活动节点不会访问到中间已经被逻辑删除的节点(unlinking);
2)若1)中导致重新链接到了对头或队尾,则通过updateHead、updateTail使被删除的节点无法从head/tail可达,最后让被删除节点自连接或者执行终结节点(GC-unlinking)。
总结
ConcurrentLinkedDeque使用了自旋+CAS的非阻塞算法来保证线程并发访问时的数据一致性。由于队列本身是一种双链表结构,所以虽然算法看起来很简单,但其实需要考虑各种并发的情况,实现复杂度较高,并且ConcurrentLinkedDeque不具备实时的数据一致性,实际运用中,如果需要一种线程安全的栈结构,可以使用ConcurrentLinkedDeque。