Michael-Scott(Michael and Scott,1996)非阻塞算法中的插入算法

本文详细介绍了Java并发编程中的非阻塞算法,特别是针对Michael-Scott非阻塞链表的插入操作。通过使用AtomicReference保证线程安全,确保在多线程环境下数据结构的一致性。ConcurrentLinkedQueue的实现中也应用了这一算法,通过原子操作避免阻塞,提高并发性能。文章深入探讨了如何在并发场景下,通过无锁算法保持数据结构的正确性和高效性。

读《Java并发编程实战》笔记。

如果在某种算法中,一个线程的失败或挂起不会导致其他线程也失败或挂起,那么这种算法就被称为非阻塞算法

如果在算法的每个步骤中都存在某个线程能够执行下去,那么这种算法也被称为无锁(Lock-Free)算法

非阻塞链表:

我们需要使用一些技巧:

  • 第一个技巧,即使在一个包含多个步骤的更新操作中,也要确保数据结构总是处于一致的状态。
  • 第二个技巧,如果当 B 到达时发现 A 正在修改数据结构,那么在数据结构中应该有足够多的信息,使得 B 能完成 A 的更新操作。

实现这两个技巧时的关键点在于:当队列处于稳定状态时,尾节点的 next 域将为空,如果队列处于中间状态,那么 tail.next 将非空。因此,如何线程都能够通过检查 tail.next 来获取队列当前的状态。而且,当队列处于中间状态时,可以通过将尾节点向前移动一个节点,从而结束其他线程正在执行的插入元素操作,并使得队列恢复为稳定状态。

import java.util.concurrent.atomic.AtomicReference;

/**
 * Michael-Scott(Michael and Scott,1996)非阻塞算法中的插入算法
 * <p>
 *
 * @author hyl
 * @version v1.0: LinkedQueue.java, v 0.1 2020/10/22 18:20 $
 */
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<>(next);
        }
    }

    // 哑节点
    private final Node<E> dummy = new Node<>(null,null);
    private final AtomicReference<Node<E>> head = new AtomicReference<>(dummy);
    private final AtomicReference<Node<E>> tail = new AtomicReference<>(dummy);


    public boolean put(E item){
        Node<E> newNode = new Node<>(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;
                    }
                }
            }
        }
    }
}

在 ConcurrentLinkedQueue 中使用的正是该算法。



    /**
     * A node from which the first live (non-deleted) node (if any)
     * can be reached in O(1) time.
     * Invariants:
     * - all live nodes are reachable from head via succ()
     * - head != null
     * - (tmp = head).next != tmp || tmp != head
     * Non-invariants:
     * - head.item may or may not be null.
     * - it is permitted for tail to lag behind head, that is, for tail
     *   to not be reachable from head!
     */
    private transient volatile Node<E> head;

    /**
     * A node from which the last node on list (that is, the unique
     * node with node.next == null) can be reached in O(1) time.
     * Invariants:
     * - the last node is always reachable from tail via succ()
     * - tail != null
     * Non-invariants:
     * - tail.item may or may not be null.
     * - it is permitted for tail to lag behind head, that is, for tail
     *   to not be reachable from head!
     * - tail.next may or may not be self-pointing to tail.
     */
    private transient volatile Node<E> tail;
    /**
     * Inserts the specified element at the tail of this queue.
     * As the queue is unbounded, this method will never return {@code false}.
     *
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws NullPointerException if the specified element is null
     */
    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 is last node
                if (p.casNext(null, newNode)) {
                    // Successful CAS is the linearization point
                    // for e to become an element of this queue,
                    // and for newNode to become "live".
                    if (p != t) // hop two nodes at a time
                        casTail(t, newNode);  // Failure is OK.
                    return true;
                }
                // Lost CAS race to another thread; re-read next
            }
            else if (p == q)
                // We have fallen off list.  If tail is unchanged, it
                // will also be off-list, in which case we need to
                // jump to head, from which all live nodes are always
                // reachable.  Else the new tail is a better bet.
                p = (t != (t = tail)) ? t : head;
            else
                // Check for tail updates after two hops.
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值