ConcurrentSkipListMap diagram


JUC high-concurrency tools (3 articles) and high-concurrency containers (N articles):

Note: Before reading this article, please master the pre-knowledge of this article: the diagram of the core principle of the jump table .

Structure of ConcurrentSkipListMap

The nodes of ConcurrentSkipListMap are mainly composed of Node, Index, HeadIndex; the following is a structure diagram of a typical ConcurrentSkipListMap instance:
Insert picture description here

1 ConcurrentSkipListMap2 structure

The nodes of ConcurrentSkipListMap are mainly composed of Node, Index, HeadIndex,
Insert picture description here

Below is the introduction of Node, Index, HeadIndex

11 Ordinary Node

/**
     * 最上层链表的头指针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 Index Node Index

 /* ---------------- 索引结点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 Node HeadIndex

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

1.1.4 ConcurrentSkipListMap2 internal class and member summary

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 Several features of ConcurrentSkipListMap:

  • The nodes of ConcurrentSkipListMap are mainly composed of Node, Index, and HeadIndex;

  • The data structure of ConcurrentSkipListMap is a linked list horizontally and vertically.

  • The bottom layer is the Node layer (data node) layer, and the upper layers are the Index layer

  • From the perspective of the vertical linked list, the leftmost is the HeadIndex layer, the right is the Index layer, and the bottom of each layer corresponds to the Node, and the vertical index points to the bottommost Node.

4 Initial state of ConcurrentSkipListMap when it is created

Initially, ConcurrentSkipListMap only has HeadIndex and Base_Header nodes. The initial state is as follows:

Insert picture description here

Let's take a look at the principle of the main methods doPut, doGet, and doRemove methods of ConcurrentSkipListMap.

5 doPut principle

The put method has mainly gone through 2 steps:

The first big step: Find a suitable location at the bottom layer and insert the Node instance of the node.

The second big step: Insert one or more IndexNode nodes of the node (the number is related to the number of layers).

5.1 The first big step: Find a suitable location and insert the Node node. details as follows:

step1) Find the predecessor jumping point b, and get the b.next node as n.

step2) Traverse to find the appropriate insertion point, create a node if n is null, add the next node in the predecessor b, if the addition is successful, jump out of the first step, if it fails, proceed to step1 again

step3) n is not null, then n is the node that should be inserted. After clarifying the position, first determine whether n is still the next node of b to prevent it from being inserted in the middle first, and then determine whether the n node is a valid node, if n is logically deleted, return to step 1 and try again. Finally, determine whether the b-node has been deleted. Next, determine whether the key of the node is the key of the node of size n, and if it is equal, replace the value of the node (indicating update value), and jump to the first step. If it is greater than it means that you have to look back n, and finally find a suitable insertion point and try to insert it. If it fails, repeat step1, and successfully end the first step.

To complete most of the first step, just insert the node into the linked list, and also need to complete the IndexNode composition of the jump list.

5.1 The second big step: construct the nodes of the jump table and adjust the jump table.

step1) Random level, even number and greater than 0.

Note: The node level means the interval of the jump table. The larger the node level, the higher the level, the fewer high-level nodes, and the larger the key interval. The higher the level, the faster the search speed when searching, starting from the largest level, the node is located step by step. For a new node, you must first determine which level it belongs to. At level 1, you don’t need to build IndexNode. After a series of determining its level, first build a series of nodes in the down direction, and then pass the head node of each layer , Connect the right direction nodes of IndexNode of the whole layer.

step2) If the level of this level is 0 (knowing that the probability of getting 0 is very large), there is no need to insert an Index node. The insertion work is over.

step2) If the level of the level<= max (the level of head, the current maximum level), generate a series of Index index nodes, and concatenate them through down members, and all levels of Index index nodes (nodes are inserted nodes) constitute Down chain, the generated Index index node starts from level 1.

step3) If the level of this level> max (the level of head, the current maximum level) (the maximum value returned by this function is also 31, that is, there are at most 31 levels of index), then increase a jump table level, Generate all levels of Index nodes starting from 1 (node ​​is an insert node) to form a down chain.

step4) Judge the level of the head node again. If the head level is higher than this level, it proves that the head is adjusted by other threads preemptively, and start over. Without preemption, rebuild the index headIndex of the head node. The node is the node of the head node. Just add the missing level. Replace the head node HeadIndex successfully out of the loop, and fail again.

The above are all nodes in the down direction to ensure that the down direction of the head includes all index levels. The latter method is to construct the connection of the right method. It should be noted here that h has become the new head node, but level is the old level.

step5) h node or h's right node r is null, there is no need to proceed, end this link

step6) r is not null, compare key with the key of node n of r. If node n is logically deleted, help it to be removed. After removal, find the next node r. The current r node is smaller than the key, then the key is still on the right, and continue to look for r. Until the location where the key should be found, that is, the r node>=key, the right of the key is r.

step7) Continue to degrade until the current insertion level is found, until the specified level is reached, the connection is constructed, and the connection fails again. If the constructed node is logically deleted, use the findNode method to delete it.

6 Illustration: The completion process of put

6.1 Add the first node

Add key=1, value = A node, the result is as shown in the figure:
Insert picture description here

Proceed as follows:

  • 1 doPut() looks for the predecessor node, and the returned b = BaseHeader, n = null

  • 2 doPut direct CAS operation to set the next node of b

  • 3 Here it is assumed that the level obtained is 0 (knowing that the probability of obtaining 0 is very large, the maximum value returned by this function is also 31, that is, there are at most 31 levels of index)

  • 4 So at this time index index node = null, the operation ends

6.2 Add the second node

Add key=2, value = B node again, and the final effect diagram is as follows:

Insert picture description here

6.3 Add the third node

Here, for the convenience of understanding, we add another node, and the final rendering is as follows:

Insert picture description here

Proceed as follows:

  • 1 doPut() looks for the predecessor node, and the returned b = node2, n = null

  • 2 doPut direct CAS operation to set the next node of b to the new node3

  • 3 Here, assuming that the obtained level is 1, then level <= max(max = 1) is established, and an index index node is initialized

  • 4 Finally find the index position to be inserted, and then perform the down link operation, so at this time the down of the index index node = null, the operation ends

This time the index layer index 1 is added

6.4 Add the 4th node

Then put node key=4 value = D (the situation is the same as Node1, Node2), the final result:

[External link image transfer failed. The source site may have an anti-leech link mechanism. It is recommended to save the image and upload it directly (img-Am5hoyuI-1604491989652)(file:///C:/Users/WUQING~1/AppData/Local/Temp /msohtmlclip1/01/clip_image006.png)]

6.5 Add the fifth node

Add key=5, value = E node, the result is as shown in the figure:

[External link image transfer failed. The source site may have an anti-leech link mechanism. It is recommended to save the image and upload it directly (img-iIncHfVK-1604491989653)(file:///C:/Users/WUQING~1/AppData/Local/Temp /msohtmlclip1/01/clip_image007.png)]

Proceed as follows:

  • 1 doPut() looks for the predecessor node, and the returned b = node4, n = null

  • 2 doPut direct CAS operation to set the next node of b to the new node5

  • 3 Assuming that the acquired level is 2, then level <= max (max = 1) does not hold, as long as level> max, it is only at the original max + 1, which means to add a layer of index

  • 4 Initialize the index linked list, there are two index nodes in total, one in the first layer, and the index linked list is a vertical linked list

  • 5 Add a level and add a new node to the vertical linked list of the original HeadIndex. The down of the new HeadIndex = the old HeadIndex, which is connected vertically, and the index of the new HeadIndex is the Index of the second layer, and the HadeIndex is connected horizontally. Up

This time the index layer index 1 is added

7 The source code of 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() finds the predecessor node

The general idea is: starting from the HeadIndex index in the upper left corner of the rectangular linked list, go to the right first, go down when you encounter a null, or> key, and repeat the search to the right and down, until you find the corresponding predecessor node (the predecessor node is less than the largest node of key)


/**
 * 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() Get the value corresponding to the node

the whole process:

  1. Find the predecessor node b of the key (at this time b.next = null || b.next> key, it means that there is no Node corresponding to the key)
  2. Then determine the relationship between b, b.next and key (some of them helpDelete operations)
/**
 * 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() delete node

The whole deletion of a ConcurrentSkipListMap is one of the highlights of the implementation of nonBlockingLinkedList in the ConcurrentSkipListMap. Why? Because this nonBlockingLinkedList also supports concurrent and safe add/delete operations from the middle of the linked list, while ConcurrentLinkedQueue only supports concurrent and safe deletion from the middle of the linked list;
delete operation:

  1. Find the corresponding node
  2. Give the node value to null, node.value = null
  3. Add a marked node to node (this.value = this remember, wow, if you don’t remember, just look at the node class)
  4. Directly delete the Node corresponding to K and the marked node through 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 Lock free programming (lock free)

Common lock free programming is generally based on the combination of CAS (Compare And Swap) + volatile: (1) CAS guarantees the atomicity of operations, and volatile guarantees the visibility of memory.

  • advantage:

1. Less overhead: no need to enter the kernel, no need to switch threads;

2. No deadlock: The longest bus lock lasts for a read+write time;

3. Only the write operation needs to use CAS, the read operation is exactly the same as the serial code, and the read and write are not mutually exclusive.

  • Disadvantages:

1. Programming is very complicated, anything can happen between two lines of code, and many common-sense assumptions are not true.

2. The CAS model covers very few cases, and CAS cannot be used to implement atomic complex operations.

12 Comparison of Key-Value Structure of Lock-Free Programming

There are currently three commonly used key-value data structures: Hash table, red-black tree, and SkipList, each of which has different advantages and disadvantages (delete operations are not considered):

  • Hash table: Insertion and search are the fastest, O(1); if a linked list is used, lock-free can be achieved; data ordering requires explicit sorting operations.

  • Red-black tree: Insertion and search are O(logn), but the constant term is small; the complexity of lock-free implementation is very high, and generally requires locking; the data is naturally ordered.

  • SkipList: Insertion and search are O(logn), but the constant item is larger than the red-black tree; the underlying structure is a linked list, which can be implemented without locks; the data is naturally ordered.

If you want to implement a key-value structure, the required functions include insertion, search, iteration, and modification, then the Hash table is not very suitable first, because the time complexity of iteration is relatively high; and the insertion of red-black trees is likely to involve The rotation and color-changing operations of multiple nodes need to be locked in the outer layer, which invisibly reduces its possible concurrency. The bottom layer of SkipList is implemented with a linked list, which can be implemented as lock free. At the same time, it has good performance (only slightly slower than the red-black tree in a single thread), which is very suitable for achieving the kind of key-value structure we need.

Therefore, the underlying storage structure of LevelDB and Redis is SkipList.


Back to ◀ Crazy Maker Circle

Crazy Maker Circle-Java high-concurrency research community, open the door to big factories for everyone


Guess you like

Origin blog.csdn.net/crazymakercircle/article/details/109498167