惊爆!深入解析 Java ConcurrentSkipListSet 使用原理

202 阅读19分钟

惊爆!深入解析 Java ConcurrentSkipListSet 使用原理

一、引言

在 Java 并发编程的世界里,数据结构的选择对于程序的性能和稳定性起着至关重要的作用。ConcurrentSkipListSet 作为 Java 并发包(java.util.concurrent)中的一员,是一个强大且独特的集合类。它为开发者在多线程环境下提供了一种高效的、线程安全的有序集合实现。

在很多实际应用场景中,我们不仅需要集合能够保证元素的唯一性,还希望元素能够按照一定的顺序排列,并且能够在高并发的情况下进行安全的读写操作。ConcurrentSkipListSet 正好满足了这些需求,它基于跳表(Skip List)数据结构实现,能够在 O(logn)O(\log n) 的平均时间复杂度内完成插入、删除和查找操作,同时支持多线程并发访问,避免了传统同步机制带来的性能瓶颈。

本文将深入剖析 ConcurrentSkipListSet 的使用原理,从源码层面进行详细解读。我们将逐步分析其构造方法、核心操作方法(如插入、删除、查找)以及并发控制机制,通过大量的源码和详细的注释,帮助读者全面理解 ConcurrentSkipListSet 的工作原理。同时,我们还将探讨其性能特点、适用场景以及与其他集合类的比较。通过阅读本文,你将对 ConcurrentSkipListSet 有一个深入而透彻的认识,能够在实际项目中更加合理地运用它。

二、ConcurrentSkipListSet 概述

2.1 基本概念

ConcurrentSkipListSet 是 Java 并发包中提供的一个线程安全的有序集合类,它实现了 NavigableSet 接口。这意味着 ConcurrentSkipListSet 中的元素是唯一的,并且会按照元素的自然顺序或者指定的比较器进行排序。

跳表(Skip List)是 ConcurrentSkipListSet 的底层数据结构。跳表是一种随机化的数据结构,它通过在每个节点中维护多个指向其他节点的指针,从而实现了快速的查找、插入和删除操作。跳表的平均时间复杂度为 O(logn)O(\log n),与平衡二叉搜索树相当,但实现起来更加简单。

2.2 特点

  • 线程安全ConcurrentSkipListSet 保证了在多线程环境下对集合的操作是线程安全的,多个线程可以同时对集合进行读写操作,而无需额外的同步机制。
  • 有序性:集合中的元素会按照自然顺序或者指定的比较器进行排序,使得我们可以方便地进行范围查找、获取最小元素、获取最大元素等操作。
  • 高效的操作:跳表数据结构使得插入、删除和查找操作的平均时间复杂度为 O(logn)O(\log n),在大规模数据场景下具有较好的性能表现。
  • 支持并发迭代ConcurrentSkipListSet 支持并发迭代,即多个线程可以同时对集合进行迭代操作,而不会抛出 ConcurrentModificationException 异常。

2.3 应用场景

由于 ConcurrentSkipListSet 具有线程安全、有序性和高效操作的特点,它适用于以下场景:

  • 排行榜系统:在游戏、电商等领域,经常需要实现排行榜功能,如玩家得分排行榜、商品销量排行榜等。ConcurrentSkipListSet 可以方便地存储和管理排行榜数据,保证数据的有序性和线程安全。
  • 缓存淘汰策略:在缓存系统中,为了保证缓存的性能,需要实现一定的淘汰策略,如 LRU(Least Recently Used)、LFU(Least Frequently Used)等。ConcurrentSkipListSet 可以用于存储缓存项,并根据缓存项的使用频率或访问时间进行排序,方便进行淘汰操作。
  • 多线程数据处理:在多线程环境下,需要对数据进行排序和去重处理时,ConcurrentSkipListSet 可以作为一个高效的解决方案。

三、ConcurrentSkipListSet 源码分析

3.1 类的定义和成员变量

// java.util.concurrent.ConcurrentSkipListSet 类的定义,继承自 AbstractSet 类并实现了 NavigableSet、Cloneable、Serializable 接口
public class ConcurrentSkipListSet<E> extends AbstractSet<E>
        implements NavigableSet<E>, Cloneable, java.io.Serializable {
    // 用于序列化和反序列化的版本号
    private static final long serialVersionUID = -2479143111061671589L;

    // 内部使用的 ConcurrentSkipListMap 实例,用于存储元素
    private final ConcurrentSkipListMap<E,Object> m;

    // 构造方法,创建一个空的 ConcurrentSkipListSet,使用元素的自然顺序进行排序
    public ConcurrentSkipListSet() {
        // 初始化 ConcurrentSkipListMap 实例
        m = new ConcurrentSkipListMap<E,Object>();
    }

    // 构造方法,创建一个空的 ConcurrentSkipListSet,使用指定的比较器进行排序
    public ConcurrentSkipListSet(Comparator<? super E> comparator) {
        // 初始化 ConcurrentSkipListMap 实例,并传入指定的比较器
        m = new ConcurrentSkipListMap<E,Object>(comparator);
    }

    // 构造方法,使用指定集合的元素初始化 ConcurrentSkipListSet,使用元素的自然顺序进行排序
    public ConcurrentSkipListSet(Collection<? extends E> c) {
        // 初始化 ConcurrentSkipListMap 实例
        m = new ConcurrentSkipListMap<E,Object>();
        // 将指定集合中的元素添加到 ConcurrentSkipListSet 中
        addAll(c);
    }

    // 构造方法,使用指定有序集合的元素初始化 ConcurrentSkipListSet,使用与指定有序集合相同的比较器进行排序
    public ConcurrentSkipListSet(SortedSet<E> s) {
        // 初始化 ConcurrentSkipListMap 实例,并传入指定有序集合的比较器
        m = new ConcurrentSkipListMap<E,Object>(s.comparator());
        // 将指定有序集合中的元素添加到 ConcurrentSkipListSet 中
        addAll(s);
    }

    // 私有构造方法,用于创建子集合
    ConcurrentSkipListSet(ConcurrentSkipListMap<E,Object> m) {
        this.m = m;
    }

    // 返回集合的迭代器,按照元素的升序排列
    public Iterator<E> iterator() {
        // 调用 ConcurrentSkipListMap 的 keySet().iterator() 方法获取迭代器
        return m.keySet().iterator();
    }

    // 返回集合的迭代器,按照元素的降序排列
    public Iterator<E> descendingIterator() {
        // 调用 ConcurrentSkipListMap 的 descendingKeySet().iterator() 方法获取迭代器
        return m.descendingKeySet().iterator();
    }

    // 返回集合的大小
    public int size() {
        // 调用 ConcurrentSkipListMap 的 size() 方法获取大小
        return m.size();
    }

    // 判断集合是否为空
    public boolean isEmpty() {
        // 调用 ConcurrentSkipListMap 的 isEmpty() 方法判断是否为空
        return m.isEmpty();
    }

    // 判断集合中是否包含指定元素
    public boolean contains(Object o) {
        // 调用 ConcurrentSkipListMap 的 containsKey() 方法判断是否包含指定元素
        return m.containsKey(o);
    }

    // 向集合中添加指定元素
    public boolean add(E e) {
        // 调用 ConcurrentSkipListMap 的 putIfAbsent() 方法添加元素,如果元素已经存在则返回 false
        return m.putIfAbsent(e, Boolean.TRUE) == null;
    }

    // 从集合中移除指定元素
    public boolean remove(Object o) {
        // 调用 ConcurrentSkipListMap 的 remove() 方法移除指定元素,如果元素存在则返回 true
        return m.remove(o, Boolean.TRUE);
    }

    // 清空集合中的所有元素
    public void clear() {
        // 调用 ConcurrentSkipListMap 的 clear() 方法清空集合
        m.clear();
    }

    // 返回集合中小于指定元素的最大元素,如果不存在则返回 null
    public E lower(E e) {
        // 调用 ConcurrentSkipListMap 的 lowerKey() 方法获取小于指定元素的最大元素
        return m.lowerKey(e);
    }

    // 返回集合中小于等于指定元素的最大元素,如果不存在则返回 null
    public E floor(E e) {
        // 调用 ConcurrentSkipListMap 的 floorKey() 方法获取小于等于指定元素的最大元素
        return m.floorKey(e);
    }

    // 返回集合中大于等于指定元素的最小元素,如果不存在则返回 null
    public E ceiling(E e) {
        // 调用 ConcurrentSkipListMap 的 ceilingKey() 方法获取大于等于指定元素的最小元素
        return m.ceilingKey(e);
    }

    // 返回集合中大于指定元素的最小元素,如果不存在则返回 null
    public E higher(E e) {
        // 调用 ConcurrentSkipListMap 的 higherKey() 方法获取大于指定元素的最小元素
        return m.higherKey(e);
    }

    // 移除并返回集合中的第一个元素,如果集合为空则返回 null
    public E pollFirst() {
        // 调用 ConcurrentSkipListMap 的 pollFirstEntry() 方法移除并返回第一个键值对,如果存在则返回键
        Map.Entry<E,Object> e = m.pollFirstEntry();
        return (e == null) ? null : e.getKey();
    }

    // 移除并返回集合中的最后一个元素,如果集合为空则返回 null
    public E pollLast() {
        // 调用 ConcurrentSkipListMap 的 pollLastEntry() 方法移除并返回最后一个键值对,如果存在则返回键
        Map.Entry<E,Object> e = m.pollLastEntry();
        return (e == null) ? null : e.getKey();
    }

    // 返回集合的逆序视图
    public NavigableSet<E> descendingSet() {
        // 创建一个新的 ConcurrentSkipListSet 实例,使用 ConcurrentSkipListMap 的逆序视图
        return new ConcurrentSkipListSet<E>(m.descendingMap());
    }

    // 返回集合中从 fromElement 到 toElement 的部分视图
    public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
                                  E toElement,   boolean toInclusive) {
        // 创建一个新的 ConcurrentSkipListSet 实例,使用 ConcurrentSkipListMap 的子映射视图
        return new ConcurrentSkipListSet<E>(m.subMap(fromElement, fromInclusive,
                                                     toElement,   toInclusive));
    }

    // 返回集合中小于 toElement 的部分视图
    public NavigableSet<E> headSet(E toElement, boolean inclusive) {
        // 创建一个新的 ConcurrentSkipListSet 实例,使用 ConcurrentSkipListMap 的头映射视图
        return new ConcurrentSkipListSet<E>(m.headMap(toElement, inclusive));
    }

    // 返回集合中大于 fromElement 的部分视图
    public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
        // 创建一个新的 ConcurrentSkipListSet 实例,使用 ConcurrentSkipListMap 的尾映射视图
        return new ConcurrentSkipListSet<E>(m.tailMap(fromElement, inclusive));
    }

    // 返回集合的比较器,如果使用自然顺序则返回 null
    public Comparator<? super E> comparator() {
        // 调用 ConcurrentSkipListMap 的 comparator() 方法获取比较器
        return m.comparator();
    }

    // 返回集合中的第一个元素,如果集合为空则抛出 NoSuchElementException 异常
    public E first() {
        // 调用 ConcurrentSkipListMap 的 firstKey() 方法获取第一个元素
        return m.firstKey();
    }

    // 返回集合中的最后一个元素,如果集合为空则抛出 NoSuchElementException 异常
    public E last() {
        // 调用 ConcurrentSkipListMap 的 lastKey() 方法获取最后一个元素
        return m.lastKey();
    }

    // 返回集合中从 fromElement 到 toElement 的部分视图,默认包含 fromElement 但不包含 toElement
    public SortedSet<E> subSet(E fromElement, E toElement) {
        // 调用 subSet 方法,指定包含 fromElement 但不包含 toElement
        return subSet(fromElement, true, toElement, false);
    }

    // 返回集合中小于 toElement 的部分视图,默认不包含 toElement
    public SortedSet<E> headSet(E toElement) {
        // 调用 headSet 方法,指定不包含 toElement
        return headSet(toElement, false);
    }

    // 返回集合中大于 fromElement 的部分视图,默认包含 fromElement
    public SortedSet<E> tailSet(E fromElement) {
        // 调用 tailSet 方法,指定包含 fromElement
        return tailSet(fromElement, true);
    }

    // 克隆集合
    public ConcurrentSkipListSet<E> clone() {
        try {
            // 创建一个新的 ConcurrentSkipListSet 实例
            ConcurrentSkipListSet<E> clone = (ConcurrentSkipListSet<E>) super.clone();
            // 初始化 ConcurrentSkipListMap 实例
            clone.m = new ConcurrentSkipListMap<E,Object>(m);
            return clone;
        } catch (CloneNotSupportedException e) {
            // 若克隆失败,抛出错误
            throw new InternalError();
        }
    }

    // 将集合中的元素写入流中,用于序列化
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // 写入默认的序列化数据
        s.defaultWriteObject();
        // 写入比较器
        s.writeObject(m.comparator());
        // 写入集合的大小
        s.writeInt(size());
        // 遍历集合中的元素并写入流中
        for (E e : this)
            s.writeObject(e);
    }

    // 从流中读取元素,用于反序列化
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // 读取默认的序列化数据
        s.defaultReadObject();
        // 读取比较器
        @SuppressWarnings("unchecked")
        Comparator<? super E> cmp = (Comparator<? super E>) s.readObject();
        // 初始化 ConcurrentSkipListMap 实例,并传入比较器
        ConcurrentSkipListMap<E,Object> map =
            new ConcurrentSkipListMap<E,Object>(cmp);
        // 读取集合的大小
        int n = s.readInt();
        // 从流中读取元素并添加到 ConcurrentSkipListMap 中
        for (int i = 0; i < n; ++i) {
            @SuppressWarnings("unchecked")
            E e = (E) s.readObject();
            map.put(e, Boolean.TRUE);
        }
        // 将初始化好的 ConcurrentSkipListMap 赋值给 m
        m = map;
    }
}

从上述代码可以看出,ConcurrentSkipListSet 内部使用了 ConcurrentSkipListMap 来存储元素。ConcurrentSkipListMap 是一个线程安全的有序映射,它的键是集合中的元素,值固定为 Boolean.TRUE。通过这种方式,ConcurrentSkipListSet 利用 ConcurrentSkipListMap 的有序性和线程安全性,实现了自身的功能。

3.2 核心操作方法

3.2.1 插入操作(add 方法)
// 向集合中添加指定元素
public boolean add(E e) {
    // 调用 ConcurrentSkipListMap 的 putIfAbsent() 方法添加元素,如果元素已经存在则返回 false
    return m.putIfAbsent(e, Boolean.TRUE) == null;
}

add 方法的实现非常简单,它直接调用了 ConcurrentSkipListMapputIfAbsent 方法。putIfAbsent 方法会尝试将指定的键值对插入到映射中,如果键已经存在,则不会进行插入操作,并返回键对应的旧值;如果键不存在,则插入键值对并返回 null。因此,add 方法通过判断 putIfAbsent 方法的返回值是否为 null 来确定元素是否成功插入。

3.2.2 删除操作(remove 方法)
// 从集合中移除指定元素
public boolean remove(Object o) {
    // 调用 ConcurrentSkipListMap 的 remove() 方法移除指定元素,如果元素存在则返回 true
    return m.remove(o, Boolean.TRUE);
}

remove 方法调用了 ConcurrentSkipListMapremove 方法,该方法会尝试移除指定键对应的值。remove 方法的第二个参数 Boolean.TRUE 表示只有当键对应的值为 Boolean.TRUE 时才进行移除操作。如果移除成功,则返回 true;否则返回 false

3.2.3 查找操作(contains 方法)
// 判断集合中是否包含指定元素
public boolean contains(Object o) {
    // 调用 ConcurrentSkipListMap 的 containsKey() 方法判断是否包含指定元素
    return m.containsKey(o);
}

contains 方法调用了 ConcurrentSkipListMapcontainsKey 方法,该方法会检查映射中是否包含指定的键。如果包含,则返回 true;否则返回 false

3.2.4 范围查找操作(subSet、headSet、tailSet 方法)
// 返回集合中从 fromElement 到 toElement 的部分视图
public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
                              E toElement,   boolean toInclusive) {
    // 创建一个新的 ConcurrentSkipListSet 实例,使用 ConcurrentSkipListMap 的子映射视图
    return new ConcurrentSkipListSet<E>(m.subMap(fromElement, fromInclusive,
                                                 toElement,   toInclusive));
}

// 返回集合中小于 toElement 的部分视图
public NavigableSet<E> headSet(E toElement, boolean inclusive) {
    // 创建一个新的 ConcurrentSkipListSet 实例,使用 ConcurrentSkipListMap 的头映射视图
    return new ConcurrentSkipListSet<E>(m.headMap(toElement, inclusive));
}

// 返回集合中大于 fromElement 的部分视图
public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
    // 创建一个新的 ConcurrentSkipListSet 实例,使用 ConcurrentSkipListMap 的尾映射视图
    return new ConcurrentSkipListSet<E>(m.tailMap(fromElement, inclusive));
}

subSetheadSettailSet 方法用于返回集合的部分视图。这些方法通过调用 ConcurrentSkipListMapsubMapheadMaptailMap 方法来获取相应的子映射视图,并将其封装成新的 ConcurrentSkipListSet 实例返回。这样,我们就可以方便地对集合进行范围查找操作。

3.3 跳表数据结构

3.3.1 跳表的基本概念

跳表(Skip List)是一种随机化的数据结构,它通过在每个节点中维护多个指向其他节点的指针,从而实现了快速的查找、插入和删除操作。跳表的基本思想是在链表的基础上增加多层索引,使得查找过程可以像二分查找一样快速。

3.3.2 跳表的节点结构
// ConcurrentSkipListMap 中的节点类
static final class Node<K,V> {
    // 键
    final K key;
    // 值
    volatile V val;
    // 指向下一个节点的引用
    volatile Node<K,V> next;

    // 构造方法,初始化节点的键、值和下一个节点
    Node(K key, V val, Node<K,V> next) {
        this.key = key;
        this.val = val;
        this.next = next;
    }

    // 移除节点的方法
    Node(Node<K,V> next) {
        this.key = null;
        this.val = null;
        this.next = next;
    }

    // 尝试将节点的下一个节点设置为指定节点
    boolean casNext(Node<K,V> cmp, Node<K,V> val) {
        // 使用 Unsafe 类的 compareAndSwapObject 方法进行 CAS 操作
        return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }

    // Unsafe 类的实例,用于进行底层操作
    private static final sun.misc.Unsafe UNSAFE;
    // next 字段的偏移量
    private static final long nextOffset;

    static {
        try {
            // 获取 Unsafe 类的实例
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            // 获取 Node 类的 Class 对象
            Class<?> k = Node.class;
            // 获取 next 字段的偏移量
            nextOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("next"));
        } catch (Exception e) {
            // 若出现异常,抛出错误
            throw new Error(e);
        }
    }
}

Node 类是跳表中的节点类,它包含三个字段:key 表示键,val 表示值,next 表示指向下一个节点的引用。casNext 方法用于尝试将节点的下一个节点设置为指定节点,使用了 CAS(Compare-And-Swap)操作来保证线程安全。

3.3.3 跳表的索引节点结构
// ConcurrentSkipListMap 中的索引节点类
static class Index<K,V> {
    // 对应的节点
    final Node<K,V> node;
    // 下一个索引节点
    final Index<K,V> down;
    // 右一个索引节点
    volatile Index<K,V> right;

    // 构造方法,初始化索引节点的节点、下一个索引节点和右一个索引节点
    Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {
        this.node = node;
        this.down = down;
        this.right = right;
    }

    // 尝试将索引节点的右一个索引节点设置为指定节点
    final boolean casRight(Index<K,V> cmp, Index<K,V> val) {
        // 使用 Unsafe 类的 compareAndSwapObject 方法进行 CAS 操作
        return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val);
    }

    // Unsafe 类的实例,用于进行底层操作
    private static final sun.misc.Unsafe UNSAFE;
    // right 字段的偏移量
    private static final long rightOffset;

    static {
        try {
            // 获取 Unsafe 类的实例
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            // 获取 Index 类的 Class 对象
            Class<?> k = Index.class;
            // 获取 right 字段的偏移量
            rightOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("right"));
        } catch (Exception e) {
            // 若出现异常,抛出错误
            throw new Error(e);
        }
    }
}

Index 类是跳表中的索引节点类,它包含三个字段:node 表示对应的节点,down 表示下一个索引节点,right 表示右一个索引节点。casRight 方法用于尝试将索引节点的右一个索引节点设置为指定节点,同样使用了 CAS 操作来保证线程安全。

3.3.4 跳表的插入操作
// ConcurrentSkipListMap 中的插入操作
private V doPut(K key, V value, boolean onlyIfAbsent) {
    // 用于记录插入路径上的索引节点
    Index<K,V>[] update = (Index<K,V>[]) new Index[MAX_LEVEL + 1];
    // 从最高层索引开始查找插入位置
    Index<K,V> q = null;
    Index<K,V> r = head;
    int level = r.level;
    for (;;) {
        Index<K,V> n = r.right;
        Node<K,V> node = r.node;
        if (n != null) {
            Node<K,V> b = n.node;
            K k = b.key;
            if (cpr.compare(key, k) > 0) {
                // 如果键大于当前节点的键,继续向右查找
                r = n;
                continue;
            }
        }
        if (q == null) {
            // 找到插入位置,记录插入路径上的索引节点
            for (int i = level; i >= 0; --i) {
                update[i] = r;
                r = r.down;
            }
            break;
        }
        // 插入新节点
        Node<K,V> newNode = new Node<K,V>(key, value, node.next);
        if (node.casNext(node.next, newNode)) {
            // 插入成功,更新索引
            int newLevel = randomLevel();
            if (newLevel > level) {
                // 如果新节点的层数大于当前最大层数,需要增加一层索引
                for (int i = level + 1; i <= newLevel; ++i) {
                    update[i] = head;
                }
                head = new Index<K,V>(null, head, null);
                head.level = newLevel;
                level = newLevel;
            }
            // 创建新的索引节点
            q = null;
            for (int i = 0; i <= newLevel; ++i) {
                Index<K,V> newIndex = new Index<K,V>(newNode, q, null);
                Index<K,V> u = update[i];
                while (u.right != null && cpr.compare(u.right.node.key, key) < 0) {
                    u = u.right;
                }
                newIndex.right = u.right;
                if (u.casRight(u.right, newIndex)) {
                    q = newIndex;
                } else {
                    // CAS 操作失败,重新查找插入位置
                    break;
                }
            }
            return null;
        }
        // CAS 操作失败,重新查找插入位置
        r = head;
        level = r.level;
        q = null;
    }
    // 如果键已经存在,根据 onlyIfAbsent 参数决定是否更新值
    for (Node<K,V> p = newNode;;) {
        if (p == null) {
            return null;
        }
        V v = p.val;
        if (v != null) {
            if (onlyIfAbsent) {
                return v;
            }
            if (p.casVal(v, value)) {
                return v;
            }
        }
        Node<K,V> b = p.next;
        if (p == b) {
            p = head;
        } else {
            p = b;
        }
    }
}

doPut 方法是 ConcurrentSkipListMap 中插入操作的核心方法。它的主要步骤如下:

  1. 从最高层索引开始查找插入位置,记录插入路径上的索引节点。
  2. 找到插入位置后,创建新的节点并尝试插入到链表中,使用 CAS 操作保证线程安全。
  3. 如果插入成功,根据随机数决定新节点的层数,并更新索引。
  4. 如果键已经存在,根据 onlyIfAbsent 参数决定是否更新值。
3.3.5 跳表的删除操作
// ConcurrentSkipListMap 中的删除操作
private V doRemove(Object key, Object value) {
    // 用于记录删除路径上的索引节点
    Index<K,V>[] update = (Index<K,V>[]) new Index[MAX_LEVEL + 1];
    // 从最高层索引开始查找删除位置
    Index<K,V> q = null;
    Index<K,V> r = head;
    int level = r.level;
    for (;;) {
        Index<K,V> n = r.right;
        Node<K,V> node = r.node;
        if (n != null) {
            Node<K,V> b = n.node;
            K k = b.key;
            if (cpr.compare(key, k) > 0) {
                // 如果键大于当前节点的键,继续向右查找
                r = n;
                continue;
            }
        }
        if (q == null) {
            // 找到删除位置,记录删除路径上的索引节点
            for (int i = level; i >= 0; --i) {
                update[i] = r;
                r = r.down;
            }
            break;
        }
        // 删除节点
        Node<K,V> p = node.next;
        if (p != null && p.key == key) {
            V v = p.val;
            if (value == null || value.equals(v)) {
                if (p.casVal(v, null)) {
                    // 标记节点为删除状态
                    Node<K,V> succ = p.next;
                    Node<K,V> marker = new Node<K,V>(succ);
                    if (p.casNext(succ, marker)) {
                        // 移除索引节点
                        for (int i = 0; i <= level; ++i) {
                            Index<K,V> u = update[i];
                            while (u.right != null && u.right.node != p) {
                                u = u.right;
                            }
                            if (u.right != null && u.right.node == p) {
                                u.casRight(u.right, u.right.right);
                            }
                        }
                        // 移除空的索引层
                        while (head.right == null && head.down != null) {
                            head = head.down;
                            head.level--;
                        }
                        return v;
                    }
                }
            }
        }
        // CAS 操作失败,重新查找删除位置
        r = head;
        level = r.level;
        q = null;
    }
    return null;
}

doRemove 方法是 ConcurrentSkipListMap 中删除操作的核心方法。它的主要步骤如下:

  1. 从最高层索引开始查找删除位置,记录删除路径上的索引节点。
  2. 找到删除位置后,尝试将节点的值设置为 null,标记节点为删除状态,使用 CAS 操作保证线程安全。
  3. 如果标记成功,创建一个标记节点并将其插入到被删除节点的后面,再次使用 CAS 操作保证线程安全。
  4. 移除索引节点,更新索引结构。
  5. 移除空的索引层,保持跳表的结构紧凑。
3.3.6 跳表的查找操作
// ConcurrentSkipListMap 中的查找操作
private Node<K,V> findNode(Object key) {
    // 从最高层索引开始查找
    Index<K,V> q = null;
    Index<K,V> r = head;
    int level = r.level;
    for (;;) {
        Index<K,V> n = r.right;
        Node<K,V> node = r.node;
        if (n != null) {
            Node<K,V> b = n.node;
            K k = b.key;
            if (cpr.compare(key, k) > 0) {
                // 如果键大于当前节点的键,继续向右查找
                r = n;
                continue;
            }
        }
        if (q == null) {
            // 找到可能的位置,向下查找
            if (r.down != null) {
                r = r.down;
                continue;
            }
            break;
        }
        // 检查节点是否匹配
        Node<K,V> p = node.next;
        if (p != null) {
            if (p.key == key) {
                return p;
            }
            if (cpr.compare(key, p.key) < 0) {
                return null;
            }
        }
        // 继续查找
        r = r.right;
    }
    return null;
}

findNode 方法是 ConcurrentSkipListMap 中查找操作的核心方法。它的主要步骤如下:

  1. 从最高层索引开始查找,根据键的大小向右移动索引节点。
  2. 当无法向右移动时,向下移动到下一层索引继续查找。
  3. 找到可能的位置后,检查节点是否匹配。
  4. 如果匹配,则返回节点;否则返回 null

3.4 并发控制机制

ConcurrentSkipListSet 的并发控制主要依赖于 ConcurrentSkipListMap 的并发控制机制。ConcurrentSkipListMap 使用了 CAS(Compare-And-Swap)操作来保证线程安全。CAS 是一种无锁算法,它通过比较内存中的值和预期值,如果相等则将内存中的值更新为新值,否则不进行更新。

ConcurrentSkipListMap 中,节点的插入、删除和更新操作都使用了 CAS 操作。例如,在插入节点时,使用 casNext 方法尝试将新节点插入到链表中;在删除节点时,使用 casVal 方法将节点的值设置为 null,使用 casNext 方法将标记节点插入到被删除节点的后面。

由于 CAS 操作是原子性的,多个线程可以并发地进行插入、删除和查找操作,而不需要加锁。当一个线程进行 CAS 操作时,如果其他线程已经修改了内存中的值,CAS 操作会失败,该线程会重试操作,直到成功为止。

四、ConcurrentSkipListSet 的性能分析

4.1 时间复杂度分析

  • 插入操作:平均时间复杂度为 O(logn)O(\log n),因为跳表的插入操作需要在多层索引中查找插入位置,并更新索引结构。
  • 删除操作:平均时间复杂度为 O(logn)O(\log n),因为跳表的删除操作需要在多层索引中查找删除位置,并更新索引结构。
  • 查找操作:平均时间复杂度为 O(logn)O(\log n),因为跳表的查找操作需要在多层索引中查找目标节点。
  • 范围查找操作:平均时间复杂度为 O(logn+k)O(\log n + k),其中 kk 是范围内的元素数量。因为跳表的范围查找操作需要先找到范围的起始位置,然后遍历范围内的元素。

4.2 空间复杂度分析

跳表的空间复杂度为 O(n)O(n),其中 nn 是集合中元素的数量。因为每个元素需要一个节点来存储,并且每个节点可能有多个索引节点。

4.3 性能特点总结

  • 高并发性能:由于采用了无锁算法,ConcurrentSkipListSet 在高并发场景下表现出色,多个线程可以高效地并发执行插入、删除和查找操作。
  • 有序性:集合中的元素会按照自然顺序或者指定的比较器进行排序,使得我们可以方便地进行范围查找、获取最小元素、获取最大元素等操作。
  • 随机化特性:跳表的随机化特性使得其性能在平均情况下接近平衡二叉搜索树,但实现起来更加简单。

五、ConcurrentSkipListSet 与其他集合类的比较

5.1 与 TreeSet 的比较

  • 线程安全TreeSet 不是线程安全的,在多线程环境下需要额外的同步机制来保证线程安全。而 ConcurrentSkipListSet 是线程安全的,多个线程可以同时对集合进行读写操作。
  • 性能:在高并发场景下,ConcurrentSkipListSet 的性能通常优于 TreeSet