ConcurrentLinkedQueue非阻塞算法的实现

背景

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的非阻塞算法概括下来有以下三点

  1. head/tail 并非总是指向队列的头 / 尾节点,也就是说允许队列处于不一致状态。 这个特性把入队 / 出队时,原本需要一起原子化执行的两个步骤分离开来,从而缩小了入队 / 出队时需要原子化更新值的范围到唯一变量。这是非阻塞算法得以实现的关键。
  2. 由于队列有时会处于不一致状态。为此,ConcurrentLinkedQueue 使用三个不变式来维护非阻塞算法的正确性
  3. 以批处理方式来更新 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;
        }
    }

参考文献

非阻塞算法在并发容器中的实现

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值