在许多队列算法中,空队列通常都包含一个“哨兵(Sentinel)”或者“哑(Dummy)节点”,并且头结点和尾节点在初始化时都指向该哨兵节点。尾节点通常要么指向哨兵节点(如果队列为空),即队列的最后一个元素,要么(当有操作正在执行更新时)指向倒数第二个元素。
使用哨兵节点与不使用哨兵节点的区别:
带哨兵节点的链表,需要额外的一个节点,插入或删除时,不论操作的位置,表头都不变,不需要额外的判断;不带哨兵节点的链表,插入或删除操作发生在第一个节点时,表头指针都要变化,需要额外的处理,在处理链表为空时,和其他情况不一样,需要单独判断一次。
@ThreadSafe
public class LinkedQueue <E> {
private static class Node <E> {
final E item;
final AtomicReference<Node<E>> next;
public Node(E item, Node<E> next) {
this.item = item;
this.next = new AtomicReference<Node<E>>(next);
}
}
private final Node<E> dummy = new Node<E>(null, null);
private final AtomicReference<Node<E>> head
= new AtomicReference<Node<E>>(dummy);
private final AtomicReference<Node<E>> tail
= new AtomicReference<Node<E>>(dummy);
public boolean put(E item) {
Node<E> newNode = new Node<E>(item, null);
while (true) {
Node<E> curTail = tail.get();
Node<E> tailNext = curTail.next.get();
if (curTail == tail.get()) {
if (tailNext != null) {
// 队列处于中间状态,推进尾节点
tail.compareAndSet(curTail, tailNext);
} else {
// 处于稳定状态,尝试插入新节点
if (curTail.next.compareAndSet(null, newNode)) {
// 插入操作成功,尝试推进尾节点
tail.compareAndSet(curTail, newNode);
return true;
}
}
}
}
}
}
稳定的两个节点队列:
插入中的三个节点:
插入完成的三个节点: