在Java并发编程场景中,当需要在多线程环境下使用有序且线程安全的集合时,ConcurrentSkipListSet是一个常用的选择。它位于java.util.concurrent包下,基于跳表数据结构实现,天生支持并发操作,不需要额外的同步措施就能保证多线程下的数据一致性。

ConcurrentSkipListSet核心特性
ConcurrentSkipListSet实现了NavigableSet接口,具备以下核心特性:
- 线程安全:所有操作都是线程安全的,支持多线程并发读写,不需要手动加锁
- 有序性:集合中的元素会按照自然排序或者自定义的比较器排序,默认是自然升序
- 非阻塞:基于无锁算法实现,并发操作的性能优于传统的同步集合
- 不允许null元素:和大多数并发集合一样,插入null元素会抛出
NullPointerException
基础使用方式
创建集合
可以通过无参构造器或者传入自定义比较器的构造器创建ConcurrentSkipListSet实例:
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.Comparator;
public class SkipListSetDemo {
public static void main(String[] args) {
// 无参构造,元素按自然排序
ConcurrentSkipListSet<Integer> naturalSet = new ConcurrentSkipListSet<>();
// 传入自定义比较器,这里实现降序排序
ConcurrentSkipListSet<Integer> descSet = new ConcurrentSkipListSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// 降序排序,o2.compareTo(o1) 改为 o1.compareTo(o2) 就是升序
return o2.compareTo(o1);
}
});
}
}
常用操作方法
ConcurrentSkipListSet提供了丰富的操作方法,和普通的Set接口方法类似,同时增加了一些导航相关的方法:
import java.util.concurrent.ConcurrentSkipListSet;
public class SkipListSetOperation {
public static void main(String[] args) {
ConcurrentSkipListSet<String> set = new ConcurrentSkipListSet<>();
// 添加元素
set.add("apple");
set.add("banana");
set.add("cherry");
// 判断元素是否存在
boolean hasApple = set.contains("apple");
System.out.println("是否包含apple:" + hasApple);
// 删除元素
set.remove("banana");
// 获取第一个元素(最小的元素)
String first = set.first();
System.out.println("第一个元素:" + first);
// 获取最后一个元素(最大的元素)
String last = set.last();
System.out.println("最后一个元素:" + last);
// 获取小于等于指定元素的最大元素
String floor = set.floor("blueberry");
System.out.println("小于等于blueberry的最大元素:" + floor);
// 获取大于等于指定元素的最小元素
String ceiling = set.ceiling("avocado");
System.out.println("大于等于avocado的最小元素:" + ceiling);
// 遍历集合
for (String item : set) {
System.out.println(item);
}
}
}
并发场景下的使用
ConcurrentSkipListSet的线程安全特性在并发场景下可以直接体现,下面的示例模拟多个线程同时向集合中添加元素:
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CountDownLatch;
public class ConcurrentSkipListSetTest {
public static void main(String[] args) throws InterruptedException {
ConcurrentSkipListSet<Integer> set = new ConcurrentSkipListSet<>();
// 计数器,等待所有线程执行完成
CountDownLatch latch = new CountDownLatch(3);
// 创建3个线程,每个线程添加5个元素
for (int i = 0; i < 3; i++) {
int threadId = i;
new Thread(() -> {
for (int j = 0; j < 5; j++) {
set.add(threadId * 10 + j);
}
latch.countDown();
}).start();
}
// 等待所有线程执行完成
latch.await();
// 打印最终集合大小,结果应该是15,不会出现数据丢失
System.out.println("集合最终大小:" + set.size());
// 打印所有元素,会是有序的
for (Integer num : set) {
System.out.print(num + " ");
}
}
}
上面的示例中,三个线程同时操作同一个ConcurrentSkipListSet实例,最终集合的大小是15,没有出现数据覆盖或者丢失的情况,说明它在并发场景下是线程安全的。
和其他线程安全集合的对比
开发者经常会把ConcurrentSkipListSet和CopyOnWriteArraySet放在一起对比,两者的区别如下:
| 对比项 | ConcurrentSkipListSet | CopyOnWriteArraySet |
|---|---|---|
| 底层数据结构 | 跳表 | 数组 |
| 是否有序 | 是 | 否 |
| 读性能 | 高,无锁读 | 高,读不需要加锁 |
| 写性能 | 高,无锁写 | 低,写操作需要复制整个数组 |
| 适用场景 | 读多写多,需要有序的场景 | 读多写少,不需要有序的场景 |
注意事项
- ConcurrentSkipListSet的迭代器是弱一致性的,迭代过程中如果集合被修改,迭代器不会抛出
ConcurrentModificationException,但可能不会反映最新的修改 - 批量操作(比如
addAll)不是原子操作,如果需要在批量操作时保证原子性,需要额外加锁 - 因为是基于跳表实现,元素的排序依赖于
Comparable接口或者传入的Comparator,如果比较规则不一致,可能会出现元素无法正确排序或者判断重复的问题
总结
ConcurrentSkipListSet是Java并发包中非常实用的线程安全有序集合,适合在多线程环境下需要有序存储且频繁进行读写操作的场景。它的无锁实现保证了较高的并发性能,使用方式也和普通Set集合类似,开发者可以快速上手。在实际项目中,可以根据是否需要有序、读写比例等需求,选择合适的线程安全集合。
ConcurrentSkipListSetJava线程安全集合跳表修改时间:2026-06-15 09:48:18