背景
ConcurrentLinkedQueue 是一个并发安全的非阻塞队列。本质利用自旋CAS的方式来完成入队和出队的操作,但是走的不是一般的玩法。
普通的非阻塞算法
以下是我本人实现的利用自旋CAS来实现并发安全得入队和出队操作。
设计思想是 每次出队后head指针指向队列头部,每次入队后tail指针指向队列尾部。
这个实现简洁明了,但是无奈Doug Lea 觉得太辣鸡,他还想再优化一下…
public class NonLockQueue<T> extends AbstractQueue<T> implements Queue<T> {
public NonLockQueue() {
Node empty = new Node(null);
head = tail = empty;
}
@Override
public boolean offer(T t) {
Node<T> n = new Node(t);
for (; ; ) {
Node<T> p = tail;
if (!p.casNext(null, n)) {
continue;
}
tail = n;
break;
}
return true;
}
@Override
public T poll() {
for (; ; ) {
Node<T> h = head;
Node<T> next = h.next;
if (next == null) {
return null;
}
if (!casHead(h, next)) {
continue;
}
h.next = h;
T val = next.item;
next.item = null;
return val;
}
}
}
Doug Lea的非阻塞算法
Doug Lea的非阻塞算法概括下来有以下三点
- head/tail 并非总是指向队列的头 / 尾节点,也就是说允许队列处于不一致状态。 这个特性把入队 / 出队时,原本需要一起原子化执行的两个步骤分离开来,从而缩小了入队 / 出队时需要原子化更新值的范围到唯一变量。这是非阻塞算法得以实现的关键。
- 由于队列有时会处于不一致状态。为此,ConcurrentLinkedQueue 使用三个不变式来维护非阻塞算法的正确性
- 以批处理方式来更新 head/tail,从整体上减少入队 / 出队操作的开销。
遵循的3个不变式
(一)基本不变式
1、队列最后一个节点的next=null
2、从head开始遍历节点,确保能通过next遍历所有的有效节点(入队时必须先进行casNext操作来保证,tail可能指向已删除节点,则从tail开始遍历不能确保遍历到最后的节点)
(二)head的不变式
1、head!=null
2、head不会指向已删除节点(casHead 之后再进行 h.next=h 来保证)
(三)tail的不变式
1、tail!=null
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
/**
* 设置当前节点为无效节点(item=null)
* 滞后再操作updateHead
*/
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) {
/**
* 表示p.item=null && p.next == null
* 如何head仍然是h并且h!=p则修改head为p
*/
updateHead(h, p);
return null;
}
else if (p == q)
/**
* 表示p为已删除节点
* 重新赋值 h = head
*/
continue restartFromHead;
else
/**
* 既然p.item==null && p.next!=null &&p.next!=p 则赋值p = q
*/
p = q;
}
}
}
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) {
/**
* 表示p当前是队列最后一个节点
*/
if (p.casNext(null, newNode)) {
if (p != t)
casTail(t, newNode);
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q) {
/**
* q!=null && p==q 说明当前节点为已删除节点
* 如果tail更新过了,则p赋值为tail,否则赋值为head【由不变式可知通过head节点可以遍历到队列最后一个节点】
*/
p = (t != (t = tail)) ? t : head;
}else
/**
* q!=null && p!=q 说明当前节点不是队列最后一个节点
* 当tail更新了则赋值p=tail,否则赋值为q
*/
p = (p != t && t != (t = tail)) ? t : q;
}
}