基于锁得算法会带来一些活跃度失败的风险。如果线程在持有锁得时候因为阻塞I/O,页面错误,或其它原因发生延迟,很可能所有线程都不能前进了。一个线程的失败或者挂起 不应该影响其他线程的失败或挂起,这样的算法称为非阻塞(nonblocking)算法;如果算法的每一步骤中都有一些线程能够继续执行,那么这样的算法称为锁自由(lock-free)算法。在线程间使用CAS进行协调,这样的算法如果能够正确的构建的话,那么它就既是非阻塞的,有事锁自由的。在实现同等功能的前提下,非阻塞算法比基于锁得算法更加复杂。创建非阻塞算法的前提是为了维护数据的一致性,解决如何把原子化范围缩小到一个唯一的变量。下面通过两个例子来说明如果设计基于非阻塞算法的数据结构。一个例子是仅需维护一个变量的栈,零一个例子就是JDK Concurrent 包里的ConcurrentLinkedQueue,这个例子展示如何通过通过非阻塞算法来维护两个变量的一致性。
public class ConcurrentStack<E> {
AtomicReference<Node<E>> top = new AtomicReference<Node<E>>();
public void push(E item)
{
Node<E> newHead = new Node<E>(item);
Node<E> oldHead;
do
{
oldHead = top.get();
newHead.next = oldHead;
}while(!top.compareAndSet(oldHead, newHead));
}
public E pop()
{
Node<E> newHead;
Node<E> oldHead;
do
{
oldHead = top.get();
if(oldHead == null)
{
return null;
}
newHead = oldHead.next;
}while(!top.compareAndSet(oldHead, newHead));
return oldHead.item;
}
private static class Node<E>
{
public final E item;
public Node<E> next;
public Node(E item)
{
this.item = item;
}
}
}
在上面的代码中,栈顶top变量是一个原子引用变量,其包含一个值和一个指向下一个元素的链接next。在Push的时候,通过创建一个新的Node,然后把这个新创建的Node的next指向当前的top节点,然后用CAS去把这个新创建的节点加入到栈中,即把新创建的节点作为栈新的top节点。Pop操作采用的是类似的思路。此处我的理解是:通过不停的轮询和CAS更新,来保证多线程环境下的数据一致性,虽然这样的轮询也需要时间,但相对获取锁及等待锁得阻塞算法,这个时间要小,而且还不存在死锁等风险。
一个链接队列比栈更加复杂,因为栈只需维护一个栈顶节点,因此通过CAS来保持一致性比较容易,但队列需要支持首尾的快速访问,因此队列需要维护首节点和队尾节点。因此为链表队列构建非阻塞算法需要考虑如何原子地更新两个节点。下面我们看ConcurrentLinkedQueue的代码是如何实现这一点的。
Node oldTail = tail;
oldTail.next = newNode;
tail = newNode;
tail.next = null;
在这个添加过程中,tail的next不为null,一旦添加完成,队列又变为稳定状态,tail的next重新被置为null。
/**
* Inserts the specified element at the tail of this queue.
*
* @return {@code true} (as specified by {@link Queue#offer})
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
Node<E> n = new Node<E>(e);
retry:
for (;;) {
Node<E> t = tail;
Node<E> p = t;
for (int hops = 0; ; hops++) {
Node<E> next = succ(p);
if (next != null) { /A
if (hops > HOPS && t != tail) //B
continue retry;
p = next;
} else if (p.casNext(null, n)) { //C
if (hops >= HOPS)
casTail(t, n); // Failure is OK. //D
return true;
} else {
p = succ(p); //E
}
}
}
}