读《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;
}
}

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

被折叠的 条评论
为什么被折叠?



