惊爆!深入解析 Java ConcurrentSkipListSet 使用原理
一、引言
在 Java 并发编程的世界里,数据结构的选择对于程序的性能和稳定性起着至关重要的作用。ConcurrentSkipListSet 作为 Java 并发包(java.util.concurrent)中的一员,是一个强大且独特的集合类。它为开发者在多线程环境下提供了一种高效的、线程安全的有序集合实现。
在很多实际应用场景中,我们不仅需要集合能够保证元素的唯一性,还希望元素能够按照一定的顺序排列,并且能够在高并发的情况下进行安全的读写操作。ConcurrentSkipListSet 正好满足了这些需求,它基于跳表(Skip List)数据结构实现,能够在 的平均时间复杂度内完成插入、删除和查找操作,同时支持多线程并发访问,避免了传统同步机制带来的性能瓶颈。
本文将深入剖析 ConcurrentSkipListSet 的使用原理,从源码层面进行详细解读。我们将逐步分析其构造方法、核心操作方法(如插入、删除、查找)以及并发控制机制,通过大量的源码和详细的注释,帮助读者全面理解 ConcurrentSkipListSet 的工作原理。同时,我们还将探讨其性能特点、适用场景以及与其他集合类的比较。通过阅读本文,你将对 ConcurrentSkipListSet 有一个深入而透彻的认识,能够在实际项目中更加合理地运用它。
二、ConcurrentSkipListSet 概述
2.1 基本概念
ConcurrentSkipListSet 是 Java 并发包中提供的一个线程安全的有序集合类,它实现了 NavigableSet 接口。这意味着 ConcurrentSkipListSet 中的元素是唯一的,并且会按照元素的自然顺序或者指定的比较器进行排序。
跳表(Skip List)是 ConcurrentSkipListSet 的底层数据结构。跳表是一种随机化的数据结构,它通过在每个节点中维护多个指向其他节点的指针,从而实现了快速的查找、插入和删除操作。跳表的平均时间复杂度为 ,与平衡二叉搜索树相当,但实现起来更加简单。
2.2 特点
- 线程安全:
ConcurrentSkipListSet保证了在多线程环境下对集合的操作是线程安全的,多个线程可以同时对集合进行读写操作,而无需额外的同步机制。 - 有序性:集合中的元素会按照自然顺序或者指定的比较器进行排序,使得我们可以方便地进行范围查找、获取最小元素、获取最大元素等操作。
- 高效的操作:跳表数据结构使得插入、删除和查找操作的平均时间复杂度为 ,在大规模数据场景下具有较好的性能表现。
- 支持并发迭代:
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 方法的实现非常简单,它直接调用了 ConcurrentSkipListMap 的 putIfAbsent 方法。putIfAbsent 方法会尝试将指定的键值对插入到映射中,如果键已经存在,则不会进行插入操作,并返回键对应的旧值;如果键不存在,则插入键值对并返回 null。因此,add 方法通过判断 putIfAbsent 方法的返回值是否为 null 来确定元素是否成功插入。
3.2.2 删除操作(remove 方法)
// 从集合中移除指定元素
public boolean remove(Object o) {
// 调用 ConcurrentSkipListMap 的 remove() 方法移除指定元素,如果元素存在则返回 true
return m.remove(o, Boolean.TRUE);
}
remove 方法调用了 ConcurrentSkipListMap 的 remove 方法,该方法会尝试移除指定键对应的值。remove 方法的第二个参数 Boolean.TRUE 表示只有当键对应的值为 Boolean.TRUE 时才进行移除操作。如果移除成功,则返回 true;否则返回 false。
3.2.3 查找操作(contains 方法)
// 判断集合中是否包含指定元素
public boolean contains(Object o) {
// 调用 ConcurrentSkipListMap 的 containsKey() 方法判断是否包含指定元素
return m.containsKey(o);
}
contains 方法调用了 ConcurrentSkipListMap 的 containsKey 方法,该方法会检查映射中是否包含指定的键。如果包含,则返回 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));
}
subSet、headSet 和 tailSet 方法用于返回集合的部分视图。这些方法通过调用 ConcurrentSkipListMap 的 subMap、headMap 和 tailMap 方法来获取相应的子映射视图,并将其封装成新的 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 中插入操作的核心方法。它的主要步骤如下:
- 从最高层索引开始查找插入位置,记录插入路径上的索引节点。
- 找到插入位置后,创建新的节点并尝试插入到链表中,使用 CAS 操作保证线程安全。
- 如果插入成功,根据随机数决定新节点的层数,并更新索引。
- 如果键已经存在,根据
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 中删除操作的核心方法。它的主要步骤如下:
- 从最高层索引开始查找删除位置,记录删除路径上的索引节点。
- 找到删除位置后,尝试将节点的值设置为
null,标记节点为删除状态,使用 CAS 操作保证线程安全。 - 如果标记成功,创建一个标记节点并将其插入到被删除节点的后面,再次使用 CAS 操作保证线程安全。
- 移除索引节点,更新索引结构。
- 移除空的索引层,保持跳表的结构紧凑。
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 中查找操作的核心方法。它的主要步骤如下:
- 从最高层索引开始查找,根据键的大小向右移动索引节点。
- 当无法向右移动时,向下移动到下一层索引继续查找。
- 找到可能的位置后,检查节点是否匹配。
- 如果匹配,则返回节点;否则返回
null。
3.4 并发控制机制
ConcurrentSkipListSet 的并发控制主要依赖于 ConcurrentSkipListMap 的并发控制机制。ConcurrentSkipListMap 使用了 CAS(Compare-And-Swap)操作来保证线程安全。CAS 是一种无锁算法,它通过比较内存中的值和预期值,如果相等则将内存中的值更新为新值,否则不进行更新。
在 ConcurrentSkipListMap 中,节点的插入、删除和更新操作都使用了 CAS 操作。例如,在插入节点时,使用 casNext 方法尝试将新节点插入到链表中;在删除节点时,使用 casVal 方法将节点的值设置为 null,使用 casNext 方法将标记节点插入到被删除节点的后面。
由于 CAS 操作是原子性的,多个线程可以并发地进行插入、删除和查找操作,而不需要加锁。当一个线程进行 CAS 操作时,如果其他线程已经修改了内存中的值,CAS 操作会失败,该线程会重试操作,直到成功为止。
四、ConcurrentSkipListSet 的性能分析
4.1 时间复杂度分析
- 插入操作:平均时间复杂度为 ,因为跳表的插入操作需要在多层索引中查找插入位置,并更新索引结构。
- 删除操作:平均时间复杂度为 ,因为跳表的删除操作需要在多层索引中查找删除位置,并更新索引结构。
- 查找操作:平均时间复杂度为 ,因为跳表的查找操作需要在多层索引中查找目标节点。
- 范围查找操作:平均时间复杂度为 ,其中 是范围内的元素数量。因为跳表的范围查找操作需要先找到范围的起始位置,然后遍历范围内的元素。
4.2 空间复杂度分析
跳表的空间复杂度为 ,其中 是集合中元素的数量。因为每个元素需要一个节点来存储,并且每个节点可能有多个索引节点。
4.3 性能特点总结
- 高并发性能:由于采用了无锁算法,
ConcurrentSkipListSet在高并发场景下表现出色,多个线程可以高效地并发执行插入、删除和查找操作。 - 有序性:集合中的元素会按照自然顺序或者指定的比较器进行排序,使得我们可以方便地进行范围查找、获取最小元素、获取最大元素等操作。
- 随机化特性:跳表的随机化特性使得其性能在平均情况下接近平衡二叉搜索树,但实现起来更加简单。
五、ConcurrentSkipListSet 与其他集合类的比较
5.1 与 TreeSet 的比较
- 线程安全:
TreeSet不是线程安全的,在多线程环境下需要额外的同步机制来保证线程安全。而ConcurrentSkipListSet是线程安全的,多个线程可以同时对集合进行读写操作。 - 性能:在高并发场景下,
ConcurrentSkipListSet的性能通常优于TreeSet,