首页 > 其他分享 >Treiber stack设计

Treiber stack设计

时间:2023-11-07 14:56:40浏览次数:31  
标签:Node return next oldTop Treiber 设计 null stack

最近看JDK11的CompletableFuture源码实现时,发现内部使用了Treiber stack,维基百科上作以下描述:

The Treiber stack algorithm is a scalable lock-free stack utilizing the fine-grained concurrency primitive compare-and-swap

Treiber stack算法是属于无锁并发栈,内部使用CAS(compare-and-swap)来实现无锁并发算法。关于CAS想必大家都很熟悉,至少会用。我们先来看看CompletableFuture的无锁并发栈的实现。

CompletableFuture的Treiber stack

在CompletableFuture内部,有一个成员变量stack,并且用volatile修饰,这个属性是最作为栈的最顶端元素,入栈和出栈都要操作这个值,必然是会出现线程并发问题,volatile是为了保证该值在多线程情况下内存可见性,且禁止指令重排序,但不保证原子性,所以对这个值的原子操作需要另外用到CAS,如下所示:

volatile Completion stack;    // Top of Treiber stack of dependent actions

其中Completion是一个抽象类,继承和实现了一大堆东西,这个我们不用去看,代码如下:

abstract static class Completion extends ForkJoinTask<Void>
    implements Runnable, AsynchronousCompletionTask {
    volatile Completion next;      // Treiber stack link

    /**
     * Performs completion action if triggered, returning a
     * dependent that may need propagation, if one exists.
     *
     * @param mode SYNC, ASYNC, or NESTED
     */
    abstract CompletableFuture<?> tryFire(int mode);

    /** Returns true if possibly still triggerable. Used by cleanStack. */
    abstract boolean isLive();

    public final void run()                { tryFire(ASYNC); }
    public final boolean exec()            { tryFire(ASYNC); return false; }
    public final Void getRawResult()       { return null; }
    public final void setRawResult(Void v) {}
}

注意其内部的next 也用volatile修饰了,这是做了什么考虑呢? 具体的Completion的子类我们也不用去看,我们来看下哪里用到这个类了,在postComplete方法中,调用了JDK9引入的VarHandle的compareAndSet方法,来做CAS,我们来看下postComplete方法的源码:

/**
 * Pops and tries to trigger all reachable dependents.  Call only
 * when known to be done.
 */
final void postComplete() {
    /*
     * On each step, variable f holds current dependents to pop
     * and run.  It is extended along only one path at a time,
     * pushing others to avoid unbounded recursion.
     */
    CompletableFuture<?> f = this; Completion h;
    while ((h = f.stack) != null ||
           (f != this && (h = (f = this).stack) != null)) {
        CompletableFuture<?> d; Completion t;
        if (STACK.compareAndSet(f, h, t = h.next)) {
            if (t != null) {
                if (f != this) {
                    pushStack(h);
                    continue;
                }
                NEXT.compareAndSet(h, t, null); // try to detach
            }
            f = (d = h.tryFire(NESTED)) == null ? this : d;
        }
    }
}

postComplete方法中,只针对入栈逻辑,入栈的代码如下:

/** Unconditionally pushes c onto stack, retrying if necessary. */
final void pushStack(Completion c) {
    do {} while (!tryPushStack(c));
}

/** Returns true if successfully pushed c onto stack. */
final boolean tryPushStack(Completion c) {
    Completion h = stack;
    NEXT.set(c, h);         // CAS piggyback
    return STACK.compareAndSet(this, h, c);
}

所以简单来看,入栈的步骤如下:

  1. 尝试入栈,利用CAS将新的节点作为栈顶元素,新节点next赋值为旧栈顶元素
  2. 尝试入栈成功,即结束;入栈失败,继续重试上面的操作

抽象实现

上面JDK中CompletableFuture的Treiber stack实现感觉有点复杂,因为有其他逻辑掺杂,代码不容易阅读,其实抽象来看,Treiber stack首先是个单向链表,链表头部即栈顶元素,在入栈和出现过程中,需要对栈顶元素进行CAS控制,防止多线程情况下数据错乱。

对应入栈过程,伪代码如下:

void push(Node new) {
  do {
      
  } while(!tryPush(new)) // 尝试入栈
}

boolean tryPush(node) {
    Node oldTop = top;
    node.next = oldTop; // 新节点next赋值为旧栈顶元素
    return CAS(oldTop, node); // 利用CAS将新的节点作为栈顶元素
}

对于出栈,要做的工作就是将原来的栈顶节点移除,等待垃圾回收;新栈顶元素CAS为第一个子元素。伪代码:

E pop() {
    Node<E> oldTop = top;
    // 判断栈是否为空,为空直接返回
    if (oldTop == null) 
        return null;
    
    while (CAS(oldTop, oldTop.next)) { // CAS 将栈顶元素为第一个子元素
        oldTop.next = null; // 旧的节点删掉next引用,等待gc
    }
    
    return oldTop.item;
}

分析完上述入栈和出栈过程,完整代码如下:

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Objects;

/**
 * 基于VarHandle实现TreiberStack
 *
 * @author mingshan
 */
public class TreiberStack<E> {
  private volatile Node<E> top;

  public void push(E item) {
    Objects.requireNonNull(item);

    Node<E> newTop = new Node<E>(item);
    do {} while (!tryPush(newTop));
  }

  private boolean tryPush(Node<E> node) {
    Node<E> oldTop = top;
    NEXT.set(node, oldTop);
    return TOP.compareAndSet(this, oldTop, node);
  }

  public E pop() {
    Node<E> oldTop = top;

    if (oldTop == null)
      return null;

    while (TOP.compareAndSet(this, oldTop, oldTop.next)) {
      NEXT.set(oldTop, null);
    }

    return oldTop.item;
  }

  public boolean isEmpty() {
    return top == null;
  }

  public int size() {
    Node<E> current = top;
    int size = 0;
    while (current != null) {
      size++;
      current = current.next;
    }
    return size;
  }

  public E peek() {
    Node<E> eNode = top;
    if (eNode == null) {
      return null;
    } else {
      return eNode.item;
    }
  }

  @Override
  public String toString() {
    if (top == null) {
      return "Stack is empty";
    } else {
      StringBuilder sb = new StringBuilder();
      Node<E> current = top;
      while (current != null) {
        sb.append(current.item).append(",");
        current = current.next;
      }

      return sb.substring(0, sb.length() -1);
    }
  }

  private static class Node<E> {
    E item;
    Node<E> next;

    Node(E item) {
      this.item = item;
    }
  }

  private static final VarHandle TOP;
  private static final VarHandle NEXT;
  static {
    try {
      MethodHandles.Lookup l = MethodHandles.lookup();
      TOP = l.findVarHandle(TreiberStack.class, "top", TreiberStack.Node.class);
      NEXT = l.findVarHandle(TreiberStack.Node.class, "next", TreiberStack.Node.class);
    } catch (ReflectiveOperationException e) {
      throw new ExceptionInInitializerError(e);
    }
    
    // Reduce the risk of rare disastrous classloading in first call to
    // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
    Class<?> ensureLoaded = LockSupport.class;
  }
}

References:


title: Treiber stack设计
tags: [数据结构, 栈, JUC]
author: Mingshan
categories: [数据结构, 栈, JUC]
date: 2019-11-25

标签:Node,return,next,oldTop,Treiber,设计,null,stack
From: https://www.cnblogs.com/mingshan/p/17793625.html

相关文章

  • 跳表的设计与实现
    链表作为一种数据结构我们是比较熟知的,相对数组来说插入和删除操作性能比较高,因为数组涉及到移位操作,但数组可以利用二分法进行快速查找,而在链表中想要获取当前元素,就必须知道该元素的上一个节点(头节点除外),这就限制了链表在查找操作的性能,试想有没有一种数据结构,在链表基础上也能......
  • 设计模式(十一)享元
    一、定义运用共享技术有效地支持大量细粒度对象的复用,享元模式是一种结构型模式。二、描述享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式。享元模式的结构较为复杂,一般结合工厂模式一起使用,在其结构图中包含了一个享元工厂类,包含以下四个角色:1、Flyweigh......
  • 设计模式---策略模式+工厂
    关键词:设计模式,策略模式,工厂模式概要现在我需要实现一个功能,是添加一路SDI输出,但是输出的协议有不同,有udp、srt等,针对不同的协议我要做不同的操作,后面还有可能添加其他的协议,因此这里面用策略模式不错。由于单纯的策略模式并不能完全消除if...else...,这里我们用了工厂模式再进......
  • 子网划分设计
    子网划分设计问题描述某公司申请了一个C类的IP地址192.128.0.3;但是该公司拥有400台主机,公司想将这些主机平均分布在两层楼进行管理,但是要求400台主机属于同一个子网,请问如何进行子网划分?选用的子网掩码是多少?请给出每层楼全体主机所设置的IP地址范围,并写出整个网络的网关地址?问......
  • 2011年春季-C语言课程设计-指导书
    C语言课程设计指导书注:请各班学习委员按学号顺序对本班同学进行分组(不允许同学自行组合),把后面所列的题目分割开交给各组保留,并组织同学按时上机。1.总体要求1)       按照名单上的顺序分配PC,按照学号的顺序每3人一组(如果剩余2人,则选择任务11;如果剩余1人,则分散到前面的组中......
  • 2011年春季-C语言课程设计-报告格式
    以下内容根据教务处最新要求制定,请严格遵守。附件:课程设计报告的内容及其文本格式1、课程设计报告要求用16k纸排版,单面打印,并装订成册,内容包括:除封面外,其他每页的页脚需要有页码。①封面(包括题目、院系、专业班级、学生学号、学生姓名、指导教师姓名、职称、起止时间等)②设计任务及......
  • 《安富莱嵌入式周报》第326期:航空航天级CANopen协议栈,开源USB PD电源和功耗分析,开源Et
    周报汇总地址:http://www.armbbs.cn/forum.php?mod=forumdisplay&fid=12&filter=typeid&typeid=104 更新一期视频教程:BSP视频教程第28期:CANopen协议栈专题,CANopen主从机组网实战,CAN词典工具使用方法以及吃透PDO玩法https://www.armbbs.cn/forum.php?mod=viewthread&tid=12161......
  • 软件开发项目文档系列之七软件详细设计:从概要到细节的深化历程
    在软件开发的旅程中,概要设计为我们提供了高层次的视角,定义了系统的整体架构和目标。然而,在实际构建软件系统之前,我们需要更进一步,将这些高层次的概念细化成具体的模块和接口,这就是软件详细设计的任务。本篇博客将带您深入了解详细设计的目录,探讨每个部分的内容和重要性,以及详细设......
  • .NET(C#) LinkedList、Queue<T>和Stack<T>的使用
    本文主要介绍.NET(C#)中,LinkedList链表、Queue<T>队列和Stack<T>堆栈的使用,以及相关的示例代码。1、LinkedList(链表)链表中元素存储内存中是不连续分配,每个元素都有记录前后节点,节点值可以重复,不能通过下标访问,泛型的使用保证类型安全,可以避免装箱拆箱,找元素就只能遍历,查找不方......
  • 这次写一下inkscape这个矢量工具的使用,怎么使用shapebuilder, 作为一个设计师应该如何
    绘图工具常用的大家都知道,一般图像有web类型,位图,压缩图,原图,矢量图,而矢量图保存东西是用的矢量保存的,所以在拉伸等变换的时候会基于矢量方向计算,所以填充等总是均匀的,不像位图,拉伸时就会是使图像中的位点变稀疏,图像变得不清晰。这里adobe的photoshop是一个调色工具,这里要记得,虽然p......