Diagrama ConcurrentSkipListMap


Herramientas de alta concurrencia de JUC (3 artículos) y contenedores de alta concurrencia (N artículos):

Nota: Antes de leer este artículo, domine los conocimientos previos de este artículo: el diagrama del principio básico de la tabla de salto .

Estructura de ConcurrentSkipListMap

Los nodos de ConcurrentSkipListMap se componen principalmente de Node, Index, HeadIndex; el siguiente es un diagrama de estructura de una instancia típica de ConcurrentSkipListMap:
Inserte la descripción de la imagen aquí

1 estructura ConcurrentSkipListMap2

Los nodos de ConcurrentSkipListMap se componen principalmente de Node, Index, HeadIndex,
Inserte la descripción de la imagen aquí

A continuación se muestra la introducción de Node, Index, HeadIndex

11 Nodo ordinario

/**
     * 最上层链表的头指针head
     */
    private transient volatile HeadIndex<K, V> head;
    /* ---------------- 普通结点Node定义 -------------- */
    static final class Node<K, V> {
        final K key;
        volatile Object value;
        volatile Node<K, V> next;


        // ...

    }

1.2 Índice de nodo de índice

 /* ---------------- 索引结点Index定义 -------------- */
  static class Index<K, V> {
     final Node<K, V> node;      // node指向最底层链表的Node结点
    final Index<K, V> down;     // down指向下层Index结点
    volatile Index<K, V> right; // right指向右边的Index结点
     // ...

    }

1.3 Head Index Nodo HeadIndex

    /* ---------------- 头索引结点HeadIndex -------------- */
    static final class HeadIndex<K, V> extends Index<K, V> {
       final int level;    // 层级
       // ...
    }
}

1.1.4 Resumen de miembros y clases internas de ConcurrentSkipListMap2

public class ConcurrentSkipListMap2<K, V> extends AbstractMap<K, V>
    implements ConcurrentNavigableMap<K, V>, Cloneable, Serializable {
    /**
   * 最底层链表的头指针BASE_HEADER
     */
    private static final Object BASE_HEADER = new Object();

    /**
   * 最上层链表的头指针head
     */
    private transient volatile HeadIndex<K, V> head;
    /* ---------------- 普通结点Node定义 -------------- */
    static final class Node<K, V> {
        final K key;
        volatile Object value;
        volatile Node<K, V> next;
       // ...
    }

    /* ---------------- 索引结点Index定义 -------------- */
    static class Index<K, V> {
        final Node<K, V> node;      // node指向最底层链表的Node结点
       final Index<K, V> down;     // down指向下层Index结点
        volatile Index<K, V> right; // right指向右边的Index结点
        // ...
    }

   /**
   *Nodes heading each level keep track of their level.
  */
    /* ---------------- 头索引结点HeadIndex -------------- */
    static final class HeadIndex<K, V> extends Index<K, V> {
        final int level;    // 层级

static final class HeadIndex<K,V> extends Index<K,V> {
    final int level;
    HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
        super(node, down, right);
        this.level = level;
    }
      // ...
  }

3 Varias características de ConcurrentSkipListMap:

  • Los nodos de ConcurrentSkipListMap se componen principalmente de Node, Index y HeadIndex;

  • La estructura de datos de ConcurrentSkipListMap es una lista enlazada horizontal y verticalmente.

  • La capa inferior es la capa de nodo (nodo de datos) y las capas superiores son la capa de índice

  • Desde la perspectiva de la lista vertical vinculada, la más a la izquierda es la capa HeadIndex, la derecha es la capa de índice, y la parte inferior de cada capa corresponde al Nodo, y el índice vertical apunta al Nodo más inferior.

4 Estado inicial de ConcurrentSkipListMap cuando se crea

Inicialmente, ConcurrentSkipListMap solo tiene nodos HeadIndex y Base_Header. El estado inicial es el siguiente:

Inserte la descripción de la imagen aquí

Echemos un vistazo al principio de los métodos principales doPut, doGet y doRemove de ConcurrentSkipListMap.

5 principio doPut

El método put ha pasado principalmente por 2 pasos:

El primer gran paso: busque una ubicación adecuada en la capa inferior e inserte la instancia de nodo del nodo.

El segundo gran paso: Inserte uno o más nodos IndexNode del nodo (el número está relacionado con el número de capas).

5.1 El primer gran paso: Encuentre una ubicación adecuada e inserte el nodo Nodo. detalles como sigue:

paso1) Encuentre el punto de salto predecesor b, y obtenga el nodo b.next como n.

paso 2) Recorra para encontrar el punto de inserción apropiado, cree un nodo si n es nulo, agregue el siguiente nodo en el predecesor b, si la adición es exitosa, salte del primer paso, si falla, continúe con el paso 1 nuevamente

paso 3) n no es nulo, entonces n es el nodo que debe insertarse. Después de aclarar la posición, primero determine si n es el siguiente nodo de b para evitar que se inserte en el medio primero, y luego determine si el n nodo es un nodo válido, si n se elimina lógicamente, regrese al paso 1 y comience otra vez. Por último, determine si se ha eliminado el nodo b. A continuación, determine si la clave del nodo es la clave del nodo de tamaño n, y si es igual, reemplace el valor del nodo (que indica el valor de actualización) y salte al primer paso. Si es mayor que significa que debe mirar hacia atrás n, y finalmente encontrar un punto de inserción adecuado e intentar insertarlo. Si falla, repita el paso 1 y finalice con éxito el primer paso.

Para completar la mayor parte del primer paso, simplemente inserte el nodo en la lista vinculada y también debe completar la composición de IndexNode de la lista de salto.

5.1 El segundo gran paso: construya los nodos de la tabla de salto y ajuste la tabla de salto.

step1) Nivel aleatorio, número par y mayor que 0.

Nota: El nivel de nodo significa el intervalo de la tabla de salto. Cuanto mayor sea el nivel de nodo, mayor será el nivel, menos nodos de alto nivel y mayor será el intervalo clave. Cuanto más alto sea el nivel, más rápida será la velocidad de búsqueda al buscar, comenzando desde el nivel más grande, el nodo se ubica paso a paso. Para un nuevo nodo, primero debes determinar a qué nivel pertenece. En el nivel 1, no necesitas construir IndexNode. Después de una serie de determinar su nivel, primero construye una serie de nodos en la dirección hacia abajo y luego pasa a través de los nodos principales de cada capa., Conecte los nodos de dirección correcta de IndexNode de toda la capa.

paso 2) Si el nivel de este nivel es 0 (sabiendo que la probabilidad de obtener 0 es muy grande), no es necesario insertar un nodo de índice. Se acabó el trabajo de inserción.

paso 2) Si el nivel del nivel <= max (el nivel de la cabeza, el nivel máximo actual), generar una serie de nodos de índice de índice y concatenarlos a través de miembros inferiores y todos los niveles de nodos de índice de índice (los nodos son nodos insertados ) constituyen la cadena descendente, el nodo de índice de índice generado comienza desde el nivel 1.

paso 3) Si el nivel de este nivel> max (el nivel de cabeza, el nivel máximo actual) (el valor máximo devuelto por esta función también es 31, es decir, hay como máximo 31 niveles de índice), entonces aumente un salto nivel de tabla, generar todos los niveles de nodos de índice comenzando desde 1 (el nodo es un nodo de inserción) para formar una cadena descendente.

paso 4) Vuelva a juzgar el nivel del nodo de la cabeza. Si el nivel de la cabeza es superior a este nivel, se demuestra que la cabeza está ajustada por otros hilos de forma preventiva, y comience de nuevo. Sin preferencia, reconstruya el índice headIndex del nodo principal. El nodo es el nodo del nodo principal. Simplemente agregue el nivel que falta. Reemplace el nodo principal HeadIndex con éxito fuera del bucle y vuelva a fallar.

Los anteriores son todos los nodos en la dirección hacia abajo para garantizar que la dirección hacia abajo de la cabeza incluya todos los niveles de índice. El último método consiste en construir la conexión del método correcto. Cabe señalar aquí que h se ha convertido en el nuevo nodo principal, pero el nivel es el nivel anterior.

paso 5) el nodo h o el nodo derecho de h r es nulo, no es necesario continuar, finalice este enlace

paso 6) r no es nulo, compare la clave con la clave del nodo n de r. Si el nodo n se elimina lógicamente, ayude a eliminarlo. Después de eliminarlo, busque el siguiente nodo r. El nodo r actual es más pequeño que la clave, entonces la clave todavía está a la derecha y continúe buscando r. Hasta la ubicación donde se debe encontrar la clave, es decir, el nodo r> = clave, la derecha de la clave es r.

paso 7) Continúe degradando hasta que se encuentre el nivel de inserción actual, hasta que se alcance el nivel especificado, se construya la conexión y la conexión vuelva a fallar.Si el nodo construido se elimina lógicamente, use el método findNode para eliminarlo.

6 Ilustración: El proceso de finalización de put

6.1 Agregar el primer nodo

Agregue clave = 1, valor = Un nodo, el resultado es como se muestra en la figura:
Inserte la descripción de la imagen aquí

Proceder de la siguiente:

  • 1 doPut () busca el nodo predecesor y devuelve b = BaseHeader, n = null

  • 2 doPut la operación CAS directa para establecer el siguiente nodo de b

  • 3 Aquí se asume que el nivel obtenido es 0 (sabiendo que la probabilidad de obtener 0 es muy grande, el valor máximo que devuelve esta función también es 31, es decir, hay como máximo 31 niveles de índice)

  • 4 Entonces, en este momento index index node = null, la operación finaliza

6.2 Agregar el segundo nodo

Agregue clave = 2, valor = nodo B nuevamente, y el diagrama de efecto final es el siguiente:

Inserte la descripción de la imagen aquí

6.3 Agregar el tercer nodo

Aquí, para facilitar la comprensión, agregamos otro nodo, y la representación final es la siguiente:

Inserte la descripción de la imagen aquí

Proceder de la siguiente:

  • 1 doPut () busca el nodo predecesor y devuelve b = nodo2, n = nulo

  • 2 doPut la operación CAS directa para establecer el siguiente nodo de b en el nuevo nodo

  • 3 Aquí, asumiendo que el nivel obtenido es 1, entonces se establece el nivel <= max (max = 1) y se inicializa un nodo de índice de índice

  • 4 Finalmente encuentre la posición del índice que se va a insertar y luego realice la operación de enlace descendente, por lo que en este momento el nodo descendente del índice índice = nulo, la operación finaliza

Esta vez se agrega el índice de la capa de índice 1

6.4 Agregar el cuarto nodo

Luego ponga la clave de nodo = 4 valor = D (la situación es la misma que Nodo1, Nodo2), el resultado final:

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo de enlace anti-sanguijuela. Se recomienda guardar la imagen y subirla directamente (img-Am5hoyuI-1604491989652) (archivo: /// C: / Users / WUQING ~ 1 / AppData / Local / Temp /msohtmlclip1/01/clip_image006.png)]

6.5 Agregar el quinto nodo

Agregue clave = 5, valor = nodo E, el resultado es como se muestra en la figura:

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo de enlace anti-sanguijuela. Se recomienda guardar la imagen y subirla directamente (img-iIncHfVK-1604491989653) (archivo: /// C: / Users / WUQING ~ 1 / AppData / Local / Temp /msohtmlclip1/01/clip_image007.png)]

Proceder de la siguiente:

  • 1 doPut () busca el nodo predecesor, y devuelve b = node4, n = null

  • 2 doPut la operación CAS directa para establecer el siguiente nodo de b en el nuevo nodo

  • 3 Suponiendo que el nivel adquirido es 2, entonces el nivel <= max (max = 1) no se mantiene, siempre que el nivel> max, esté solo en el máximo original + 1, lo que significa agregar una capa de índice

  • 4 Inicialice la lista vinculada al índice, hay dos nodos de índice en total, uno en la primera capa, y la lista vinculada al índice es una lista vinculada vertical

  • 5 Agregue un nivel, agregue un nuevo nodo a la lista vertical vinculada del HeadIndex original, la parte inferior del nuevo HeadIndex = el antiguo HeadIndex, que está conectado verticalmente, y el índice del nuevo HeadIndex es el Índice de la segunda capa, y el HadeIndex está conectado horizontalmente con el Index Up

Esta vez se agrega el índice de la capa de índice 1

7 El código fuente de put

/**
 * Main insetion method. Adds element if not present, or
 * replaces value if present and onlyIfAbsent is false.
 *
 * @param key the key
 * @param value the values that must be associated with key
 * @param onlyIfAbstsent if should not insert if already present
 * @return the old value, or null if newly inserted
 */
private V doPut(K key, V value, boolean onlyIfAbstsent){
    Node<K, V> z; // adde node
    if(key == null){
        throw new NullPointerException();
    }
    Comparator<? super K> cmp = comparator;
    outer:
    for(;;){
        // 0.
        for(Node<K, V> b = findPredecessor(key, cmp), n = b.next;;){ // 1. 将 key 对应的前继节点找到, b 为前继节点, n是前继节点的next, 若没发生 条件竞争, 最终 key在 b 与 n 之间 (找到的b在 base_level 上)
            if(n != null){ // 2. n = null时 b 是链表的最后一个节点, key 直接插到 b 之后 (调用 b.casNext(n, z))
                Object v; int c;
                Node<K, V> f = n.next; // 3 获取 n 的右节点
                if(n != b.next){ // 4. 条件竞争(另外一个线程在b之后插入节点, 或直接删除节点n), 则 break 到位置 0, 重新
                    break ;
                }
                if((v = n.value) == null){ // 4. 若 节点n已经删除, 则 调用 helpDelete 进行帮助删除 (详情见 helpDelete), 则 break 到位置 0, 重新来
                    n.helpDelete(b, f);
                    break ;
                }

                if(b.value == null || v == n){ // 5. 节点b被删除中 ,则 break 到位置 0, 调用 findPredecessor 帮助删除 index 层的数据, 至于 node 层的数据 会通过 helpDelete 方法进行删除
                    break ;
                }
                if((c = cpr(cmp, key, n.key)) > 0){ // 6. 若 key 真的 > n.key (在调用 findPredecessor 时是成立的), 则进行 向后走
                    b = n;
                    n = f;
                    continue ;
                }
                if(c == 0){ // 7. 直接进行赋值
                    if(onlyIfAbstsent || n.casValue(v, value)){
                        V vv = (V) v;
                        return vv;
                    }
                    break ; // 8. cas 竞争条件失败 重来
                }
                // else c < 0; fall through
            }
            // 9. 到这边时 n.key > key > b.key
            z = new Node<K, V> (key, value, n);
            if(!b.casNext(n, z)){
                break ; // 10. cas竞争条件失败 重来
            }
            break outer; // 11. 注意 这里 break outer 后, 上面的 for循环不会再执行, 而后执行下面的代码, 这里是break 不是 continue outer, 这两者的效果是不一样的
        }
    }

    int rnd = KThreadLocalRandom.nextSecondarySeed();
    if((rnd & 0x80000001) == 0){ // 12. 判断是否需要添加level
        int level = 1, max;
        while(((rnd >>>= 1) & 1) != 0){
            ++level;
        }
        // 13. 上面这段代码是获取 level 的, 我们这里只需要知道获取 level 就可以 (50%的几率返回0,25%的几率返回1,12.5%的几率返回2...最大返回31。)
        Index<K, V> idx = null;
        HeadIndex<K, V> h = head;
        if(level <= (max = h.level)){ // 14. 初始化 max 的值, 若 level 小于 max , 则进入这段代码 (level 是 1-31 之间的随机数)
            for(int i = 1; i <= level; ++i){
                idx = new Index<K, V>(z, idx, null); // 15 添加 z 对应的 index 数据, 并将它们组成一个上下的链表(index层是上下左右都是链表)
            }
        }
        else{ // 16. 若 level > max 则只增加一层 index 索引层
            level = max + 1; // 17. 跳表新的 level 产生
            Index<K, V>[] idxs = (Index<K, V>[])new Index<?, ?>[level + 1];
            for(int i = 1; i <= level; ++i){
                idxs[i] = idx = new Index<K, V>(z, idx, null);
            }
            for(;;){
                h = head;
                int oldLevel = h.level; // 18. 获取老的 level 层
                if(level <= oldLevel){ // 19. 另外的线程进行了index 层增加操作, 所以 不需要增加 HeadIndex 层数
                    break;
                }
                HeadIndex<K, V> newh = h;
                Node<K, V> oldbase = h.node; // 20. 这里的 oldbase 就是BASE_HEADER
                for(int j = oldLevel+1; j <= level; ++j){ // 21. 这里其实就是增加一层的 HeadIndex (level = max + 1)
                    newh = new HeadIndex<K, V>(oldbase, newh, idxs[j], j); // 22. idxs[j] 就是上面的 idxs中的最高层的索引
                }
                if(casHead(h, newh)){ // 23. 这只新的 headIndex
                    h = newh;  // 24. 这里的 h 变成了 new HeadIndex
                    idx = idxs[level = oldLevel];  // 25. 这里的 idx 上从上往下第二层的 index 节点 level 也变成的 第二
                    break;
                }
            }
        }

        // find insertion points and splice in
        splice:
        for(int insertionLevel = level;;){ // 26. 这时的 level 已经是 第二高的 level(若上面 步骤19 条件竞争失败, 则多出的 index 层其实是无用的, 因为 那是 调用 Index.right 是找不到它的)
            int j = h.level;
            for(Index<K, V> q = h, r = q.right, t = idx;;){ // 27. 初始化对应的数据
                if(q == null || t == null){ // 28. 节点都被删除 直接 break出去
                    break splice;
                }
                if(r != null){
                    Node<K, V> n = r.node;
                    // compare before deletion check avoids needing recheck
                    int c = cpr(cmp, key, n.key);
                    if(n.value == null){ // 29. 老步骤, 帮助index 的删除
                        if(!q.unlink(r)){
                            break ;
                        }
                        r = q.right; // 30. 向右进行遍历
                        continue ;
                    }

                    if(c > 0){ // 31. 向右进行遍历
                        q = r;
                        r = r.right;
                        continue ;
                    }
                }

                // 32.
                // 代码运行到这里, 说明 key < n.key
                // 第一次运行到这边时, j 是最新的 HeadIndex 的level j > insertionLevel 非常用可能, 而下面又有 --j, 所以终会到 j == insertionLevel
                if(j == insertionLevel){
                    if(!q.link(r, t)){ // 33. 将 index t 加到 q 与 r 中间, 若条件竞争失败的话就重试
                        break ; // restrt
                    }
                    if(t.node.value == null){ // 34. 若这时 node 被删除, 则开始通过 findPredecessor 清理 index 层, findNode 清理 node 层, 之后直接 break 出去, doPut调用结束
                        findNode(key);
                        break splice;
                    }
                    if(--insertionLevel == 0){ // 35. index 层添加OK, --1 为下层插入 index 做准备
                        break splice;
                    }
                }

                /**
                 * 下面这行代码其实是最重要的, 理解这行代码, 那 doPut 就差不多了
                 * 1). --j 要知道 j 是 newhead 的level, 一开始一定 > insertionLevel的, 通过 --1 来为下层操作做准备 (j 是 headIndex 的level)
                 * 2). 通过 19. 21, 22 步骤, 个人认为 --j >= insertionLevel 是横成立, 而 --j 是必须要做的
                 * 3) j 经过几次--1, 当出现 j < level 时说明 (j+1) 层的 index已经添加成功, 所以处理下层的 index
                 */
                if(--j >= insertionLevel && j < level){
                    t = t.down;
                }
                /** 到这里时, 其实有两种情况
                 *  1) 还没有一次index 层的数据插入
                 *  2) 已经进行 index 层的数据插入, 现在为下一层的插入做准备
                 */
                q = q.down; // 从 index 层向下进行查找
                r = q.right;

            }
        }
    }
    return null;
}

8 findPredecessor () encuentra el nodo predecesor

La idea general es: comenzando desde el índice HeadIndex en la esquina superior izquierda de la lista enlazada rectangular, vaya primero a la derecha, baje cuando encuentre una tecla nula, o>, y repita hacia la derecha y hacia abajo para encontrar el predecesor correspondiente nodo (el nodo predecesor es menor que el nodo más grande de la clave)


/**
 * Returns a base-level node with key strictly less than given key,
 * or the base-level header if there is no such node. Also
 * unlinks indexes to deleted nodes found along the way. Callers
 * rely on this side-effect of clearing indices to deleted nodes
 * @param key the key
 * @return a predecessor of the key
 */
private Node<K, V> findPredecessor(Object key, Comparator<? super K> cmp){
    if(key == null)
        throw new NullPointerException(); // don't postpone errors
    for(;;){
        for(Index<K, V> q = head, r = q.right, d;;){ // 1. 初始化数据 q 是head, r 是 最顶层 h 的右Index节点
            if(r != null){ // 2. 对应的 r =  null, 则进行向下查找
                Node<K, V> n = r.node;
                K k = n.key;
                if(n.value == null){ // 3. n.value = null 说明 节点n 正在删除的过程中
                    if(!q.unlink(r)){ // 4. 在 index 层直接删除 r 节点, 若条件竞争发生直接进行break 到步骤1 , 重新从 head 节点开始查找
                        break; // restart
                    }
                    r = q.right; //reread r // 5. 删除 节点r 成功, 获取新的 r 节点, 回到步骤 2 (还是从这层索引开始向右遍历, 直到 r == null)
                    continue;
                }

                if(cpr(cmp, key, k) > 0){ // 6. 若 r.node.key < 参数key, 则继续向右遍历, continue 到 步骤 2处, 若 r.node.key >  参数key 直接跳到 步骤 7
                    q = r;
                    r = r.right;
                    continue;
                }
            }

            if((d = q.down) == null){ // 7. 到这边时, 已经到跳表的数据层, q.node < key < r的 或q.node < key 且 r == null; 所以直接返回 q.node
                return q.node;
            }

            q = d; // 8 未到数据层, 进行重新赋值向下走 (为什么向下走呢? 回过头看看 跳表, 原来 上层的index 一般都是比下层的 index 个数少的)
            r = d.right;
        }
    }
}

9. doGet () Obtiene el valor correspondiente al nodo

Todo el proceso:

  1. Busque el nodo predecesor b de la clave (en este momento b.next = null || b.next> key, significa que no hay ningún nodo correspondiente a la clave)
  2. Luego determine la relación entre b, b.next y key (algunos de ellos ayudan a eliminar operaciones)
/**
 * Gets value for key. Almost the same as findNode, but returns
 * the found value (to avoid retires during ret-reads)
 *
 *  这个 doGet 方法比较简单
 * @param key the key
 * @return the value, or null if absent
 */
private V doGet(Object key){
    if(key == null){
        throw new NullPointerException();
    }
    Comparator<? super K> cmp = comparator;
    outer:
    for(;;){
        for(Node<K, V> b = findPredecessor(key, cmp), n = b.next;;){ // 1. 获取 key 的前继节点 b, 其实这时 n.key >= key
            Object v; int c;
            if(n == null){ // 2. n == null 说明 key 对应的 node 不存在 所以直接 return null
                break outer;
            }
            Node<K, V> f = n.next;
            if(n != b.next){ // 3. 有另外的线程修改数据, 重新来
                break ;
            }
            if((v = n.value) == null){ // 4. n 是被删除了的节点, 进行helpDelete 后重新再来
                n.helpDelete(b, f);
                break ;
            }
            if(b.value == null || v == n){ // 5. b已经是删除了的节点, 则 break 后再来
                break ;
            }
            if((c = cpr(cmp, key, n.key)) == 0){ // 6. 若 n.key = key 直接返回结果, 这里返回的结果有可能是 null
                V vv = (V) v;
                return vv;
            }
            if(c < 0){ // 7. c < 0说明不存在 key 的node 节点
                break outer;
            }
            // 8. 运行到这一步时, 其实是 在调用 findPredecessor 后又有节点添加到 节点b的后面所致
            b = n;
            n = f;
        }
    }

    return null;
}

10. doRemove () eliminar nodo

La eliminación completa de un ConcurrentSkipListMap es uno de los aspectos más destacados de la implementación de nonBlockingLinkedList en ConcurrentSkipListMap. ¿Por qué? Porque este nonBlockingLinkedList también admite operaciones de adición / eliminación simultáneas y seguras desde el medio de la lista vinculada, mientras que ConcurrentLinkedQueue solo admite la eliminación simultánea y segura. desde el medio de la lista enlazada;
eliminar operación:

  1. Encuentra el nodo correspondiente
  2. Dar el valor del nodo a nulo, node.value = null
  3. Agregue un nodo marcado al nodo (this.value = this recuerde, guau, si no lo recuerda, solo mire la clase de nodo)
  4. Elimine directamente el Nodo correspondiente a K y el nodo marcado a través de CAS
/**
 * Main deletion method. Locates node, nulls value, appends a
 * deletion marker, unlinks predecessor, removes associated index
 * nodes, and possibly reduces head index level
 *
 * Index nodes are cleared out simply by calling findPredecessor.
 * which unlinks indexes to deleted nodes found along path to key,
 * which will include the indexes to this node. This is node
 * unconditionally. We can't check beforehand whether there are
 * indexes hadn't been inserted yet for this node during initial
 * search for it, and we'd like to ensure lack of garbage
 * retention, so must call to be sure
 *
 * @param key the key
 * @param value if non-null, the value that must be
 *              associated with key
 * @return the node, or null if not found
 */
final V doRemove(Object key, Object value){
    if(key == null){
        throw new NullPointerException();
    }
    Comparator<? super K> cmp = comparator;
    outer:
    for(;;){
        for(Node<K, V> b = findPredecessor(key, cmp), n = b.next;;){ // 1. 获取对应的前继节点 b
            Object v; int c;
            if(n == null){ // 2. 节点 n 被删除 直接 return null 返回 , 因为理论上 b.key < key < n.key
                break outer;
            }
            Node<K, V> f = n.next;
            if(n != b.next){ // 3. 有其他线程在 节点b 后增加数据, 重来
                break ;
            }
            if((v = n.value) == null){ // 4. 节点 n 被删除, 调用 helpDelete 后重来
                n.helpDelete(b, f);
                break ;
            }

            if(b.value == null || v == n){ // 5. 节点 b 删除, 重来 调用findPredecessor时会对 b节点对应的index进行清除, 而b借点吧本身会通过 helpDelete 来删除
                break ;
            }
            if((c = cpr(cmp, key, n.key)) < 0){ // 6. 若n.key < key 则说明 key 对应的节点就不存在, 所以直接 return
                break outer;
            }

            if(c > 0){ // 7. c>0 出现在 有其他线程在本方法调用findPredecessor后又在b 后增加节点, 所以向后遍历
                b = n;
                n = f;
                continue ;
            }

            if(value != null && !value.equals(v)){ // 8. 若 前面的条件为真, 则不进行删除 (调用 doRemove 时指定一定要满足 key value 都相同, 具体看 remove 方法)
                break outer;
            }
            if(!n.casValue(v, null)){ // 9. 进行数据的删除
                break ;
            }
            if(!n.appendMarker(f) || !b.casNext(n, f)){ // 10. 进行 marker 节点的追加, 这里的第二个 cas 不一定会成功, 但没关系的 (第二个 cas 是删除 n节点, 不成功会有  helpDelete 进行删除)
                findNode(key);  // 11. 对 key 对应的index 进行删除
            }
            else{
                findPredecessor(key, cmp); //12. 对 key 对应的index 进行删除 10进行操作失败后通过 findPredecessor 进行index 的删除
                if(head.right == null){
                    tryReduceLevel(); // 13. 进行headIndex 对应的index 层的删除
                }
            }

            V vv = (V) v;
            return vv;

        }
    }

    return null;
}

11 Programación sin bloqueo (sin bloqueo)

La programación libre de bloqueo común se basa generalmente en la combinación de CAS (Compare And Swap) + volátil: (1) CAS garantiza la atomicidad de las operaciones y volátil garantiza la visibilidad de la memoria.

  • ventaja:

1. Menos gastos generales: no es necesario ingresar al kernel, no es necesario cambiar de hilo;

2. Sin interbloqueo: el bloqueo de bus más largo dura un tiempo de lectura + escritura;

3. Solo la operación de escritura necesita usar CAS, la operación de lectura es exactamente la misma que el código de serie, y la lectura y escritura no son mutuamente excluyentes.

  • Desventajas:

1. La programación es muy complicada, cualquier cosa puede suceder entre dos líneas de código y muchas suposiciones de sentido común no son ciertas.

2. El modelo CAS cubre muy pocos casos, y CAS no se puede utilizar para implementar operaciones atómicas complejas.

12 Comparación de la estructura de valor clave de la programación sin bloqueo

Actualmente, existen tres estructuras de datos de valor-clave de uso común: tabla hash, árbol rojo-negro y SkipList, cada una de las cuales tiene diferentes ventajas y desventajas (no se consideran las operaciones de eliminación):

  • Tabla hash: Insertar y buscar son los más rápidos, O (1); si usa una lista vinculada, puede lograr un bloqueo libre; el orden de datos requiere operaciones de clasificación explícitas.

  • Árbol rojo-negro: la inserción y la búsqueda son O (logn), pero el término constante es pequeño; la complejidad de la implementación sin bloqueo es muy alta y generalmente requiere bloqueo; los datos están ordenados de forma natural.

  • SkipList: la inserción y la búsqueda son O (logn), pero el elemento constante es más grande que el árbol rojo-negro; la estructura subyacente es una lista vinculada, que se puede implementar sin bloqueos; los datos están ordenados naturalmente.

Si desea implementar una estructura clave-valor, las funciones requeridas incluyen inserción, búsqueda, iteración y modificación, entonces la tabla Hash no es muy adecuada primero, porque la complejidad de tiempo de la iteración es relativamente alta; y la inserción de red- Es probable que los árboles negros involucren Las operaciones de rotación y cambio de color de múltiples nodos deben bloquearse en la capa externa, lo que reduce de manera invisible su posible concurrencia. La capa inferior de SkipList se implementa con una lista vinculada, que se puede implementar sin bloqueo. Al mismo tiempo, tiene un buen rendimiento (solo un poco más lento que el árbol rojo-negro en un solo hilo), lo cual es muy adecuado para lograr el tipo de estructura clave-valor que necesitamos.

Por lo tanto, la estructura de almacenamiento subyacente de LevelDB y Redis es SkipList.


Volver a ◀ Círculo de creadores de locos

Comunidad de investigación de alta concurrencia de Crazy Maker Circle-Java, abre la puerta a grandes fábricas para todos


Supongo que te gusta

Origin blog.csdn.net/crazymakercircle/article/details/109498167
Recomendado
Clasificación