在Java的并发集合体系中,ConcurrentSkipListMap是少数支持线程安全且元素有序的Map实现,它不需要额外的同步措施就能在多线程环境下安全使用,同时默认按照键的自然顺序或者构造时传入的比较器排序,非常适合需要并发访问且要求元素有序的场景。
ConcurrentSkipListMap核心特性
ConcurrentSkipListMap的内部基于跳表(Skip List)数据结构实现,跳表是一种可以替代平衡树的有序数据结构,插入、删除、查询的时间复杂度都是O(log n),同时实现难度比平衡树更低,并发控制上也更容易扩展。
它的核心特性主要有以下几点:
- 线程安全:所有操作都不需要外部同步,支持多线程并发读写,不会抛出ConcurrentModificationException。
- 有序性:默认按照键的自然顺序排序,也可以在构造时传入自定义的
Comparator实现指定排序规则。 - 弱一致性:迭代器是弱一致性的,不会抛出并发修改异常,但是可能不会反映迭代过程中其他线程的修改。
- 支持导航操作:提供了很多类似排序相关的导航方法,比如获取第一个键、最后一个键、小于等于某个键的最大键等。
基本使用方式
构造方法说明
ConcurrentSkipListMap提供了多种构造方法,开发者可以根据需要选择:
import java.util.Comparator;
import java.util.concurrent.ConcurrentSkipListMap;
public class SkipListMapDemo {
public static void main(String[] args) {
// 1. 无参构造,按照键的自然顺序排序,要求键实现Comparable接口
ConcurrentSkipListMap<Integer, String> naturalOrderMap = new ConcurrentSkipListMap<>();
// 2. 传入自定义Comparator,按照指定规则排序
// 这里实现倒序排序,键越大越靠前
ConcurrentSkipListMap<Integer, String> customOrderMap = new ConcurrentSkipListMap<>(
new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
}
);
// 3. 传入已有的Map初始化,会按照自然顺序或者比较器排序原有元素
// ConcurrentSkipListMap<Integer, String> initMap = new ConcurrentSkipListMap<>(otherMap);
}
}
常用基础操作
ConcurrentSkipListMap的基础操作和普通Map类似,但是额外提供了很多有序相关的导航方法:
import java.util.concurrent.ConcurrentSkipListMap;
public class BasicOperationDemo {
public static void main(String[] args) {
ConcurrentSkipListMap<Integer, String> map = new ConcurrentSkipListMap<>();
// 添加元素
map.put(3, "value3");
map.put(1, "value1");
map.put(2, "value2");
// 获取元素
String val = map.get(1); // 返回value1
// 删除元素
map.remove(2);
// 判断包含
boolean hasKey = map.containsKey(3); // 返回true
// 获取第一个键和最后一个键
Integer firstKey = map.firstKey(); // 返回1
Integer lastKey = map.lastKey(); // 返回3
// 获取小于等于指定键的最大键
Integer floorKey = map.floorKey(3); // 返回3
// 获取大于等于指定键的最小键
Integer ceilingKey = map.ceilingKey(2); // 返回3
// 遍历元素,按照键的顺序输出
map.forEach((k, v) -> System.out.println(k + ":" + v));
}
}
并发场景下的使用示例
在多线程并发写入的场景下,ConcurrentSkipListMap不需要额外的同步措施就能保证线程安全,下面的例子模拟多个线程同时向Map中写入数据,最终遍历结果依然是有序的:
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrentDemo {
public static void main(String[] args) throws InterruptedException {
ConcurrentSkipListMap<Integer, String> map = new ConcurrentSkipListMap<>();
ExecutorService executor = Executors.newFixedThreadPool(3);
// 三个线程同时写入数据
for (int i = 0; i < 3; i++) {
int threadId = i;
executor.submit(() -> {
for (int j = 0; j < 5; j++) {
int key = threadId * 5 + j;
map.put(key, "thread" + threadId + "_value" + j);
}
});
}
// 等待所有线程执行完成
executor.shutdown();
while (!executor.isTerminated()) {
Thread.sleep(100);
}
// 遍历结果,会按照key的自然顺序输出
System.out.println("遍历结果:");
map.forEach((k, v) -> System.out.println(k + ":" + v));
}
}
和其他并发Map的对比
Java中常用的并发Map还有ConcurrentHashMap,两者的核心差异如下:
| 对比项 | ConcurrentSkipListMap | ConcurrentHashMap |
|---|---|---|
| 有序性 | 支持,默认自然顺序或者自定义比较器 | 不支持,遍历顺序不确定 |
| 数据结构 | 跳表 | 数组+链表+红黑树 |
| 时间复杂度 | 插入、删除、查询O(log n) | 插入、删除、查询O(1)(理想情况) |
| 适用场景 | 需要有序且并发访问的场景 | 不需要排序的高并发读写场景 |
注意事项
- 键必须支持排序:如果使用无参构造,键必须实现
Comparable接口,否则会抛出ClassCastException。 - 自定义比较器要一致:如果传入自定义
Comparator,要保证比较规则和equals方法逻辑一致,避免出现逻辑错误。 - 弱一致性迭代器:迭代过程中如果其他线程修改了Map,迭代器可能不会反映这些修改,需要根据业务场景判断是否可以接受。
- 不支持null键和null值:和大多数并发集合一样,ConcurrentSkipListMap不允许插入null键或者null值,否则会抛出NullPointerException。
总结来说,当需要在Java中实现有序的并发Map时,优先选择ConcurrentSkipListMap,它既满足了线程安全的要求,又能保证元素的有序性,使用方式也和普通的Map基本一致,学习成本很低。
ConcurrentSkipListMap有序并发MapJava并发集合跳表线程安全Map修改时间:2026-06-23 23:33:44