并发容器之ConcurrentSkipListSet

本文详细介绍了Java并发集合ConcurrentSkipListSet的特性和使用方法。内容涵盖数据结构、线程安全机制、构造方法及常用API,并通过示例代码展示了在多线程环境下与TreeSet的不同表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概要

本章对Java.util.concurrent包中的ConcurrentSkipListSet类进行详细的介绍。内容包括:
ConcurrentSkipListSet介绍
ConcurrentSkipListSet原理和数据结构
ConcurrentSkipListSet函数列表
ConcurrentSkipListSet源码(JDK1.7.0_40版本)
ConcurrentSkipListSet示例

转载请注明出处:http://www.cnblogs.com/skywang12345/p/3498634.html

 

ConcurrentSkipListSet介绍

ConcurrentSkipListSet是线程安全的有序的集合,适用于高并发的场景。
ConcurrentSkipListSet和TreeSet,它们虽然都是有序的集合。但是,第一,它们的线程安全机制不同,TreeSet是非线程安全的,而ConcurrentSkipListSet是线程安全的。第二,ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,而TreeSet是通过TreeMap实现的。

 

ConcurrentSkipListSet原理和数据结构

ConcurrentSkipListSet的数据结构,如下图所示:

说明
(01) ConcurrentSkipListSet继承于AbstractSet。因此,它本质上是一个集合。
(02) ConcurrentSkipListSet实现了NavigableSet接口。因此,ConcurrentSkipListSet是一个有序的集合。
(03) ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的。它包含一个ConcurrentNavigableMap对象m,而m对象实际上是ConcurrentNavigableMap的实现类ConcurrentSkipListMap的实例。ConcurrentSkipListMap中的元素是key-value键值对;而ConcurrentSkipListSet是集合,它只用到了ConcurrentSkipListMap中的key!

 

ConcurrentSkipListSet函数列表

复制代码
// 构造一个新的空 set,该 set 按照元素的自然顺序对其进行排序。
ConcurrentSkipListSet()
// 构造一个包含指定 collection 中元素的新 set,这个新 set 按照元素的自然顺序对其进行排序。
ConcurrentSkipListSet(Collection<? extends E> c)
// 构造一个新的空 set,该 set 按照指定的比较器对其元素进行排序。
ConcurrentSkipListSet(Comparator<? super E> comparator)
// 构造一个新 set,该 set 所包含的元素与指定的有序 set 包含的元素相同,使用的顺序也相同。
ConcurrentSkipListSet(SortedSet<E> s)

// 如果此 set 中不包含指定元素,则添加指定元素。
boolean add(E e)
// 返回此 set 中大于等于给定元素的最小元素;如果不存在这样的元素,则返回 null。
E ceiling(E e)
// 从此 set 中移除所有元素。
void clear()
// 返回此 ConcurrentSkipListSet 实例的浅表副本。
ConcurrentSkipListSet<E> clone()
// 返回对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回 null。
Comparator<? super E> comparator()
// 如果此 set 包含指定的元素,则返回 true。
boolean contains(Object o)
// 返回在此 set 的元素上以降序进行迭代的迭代器。
Iterator<E> descendingIterator()
// 返回此 set 中所包含元素的逆序视图。
NavigableSet<E> descendingSet()
// 比较指定对象与此 set 的相等性。
boolean equals(Object o)
// 返回此 set 中当前第一个(最低)元素。
E first()
// 返回此 set 中小于等于给定元素的最大元素;如果不存在这样的元素,则返回 null。
E floor(E e)
// 返回此 set 的部分视图,其元素严格小于 toElement。
NavigableSet<E> headSet(E toElement)
// 返回此 set 的部分视图,其元素小于(或等于,如果 inclusive 为 true)toElement。
NavigableSet<E> headSet(E toElement, boolean inclusive)
// 返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回 null。
E higher(E e)
// 如果此 set 不包含任何元素,则返回 true。
boolean isEmpty()
// 返回在此 set 的元素上以升序进行迭代的迭代器。
Iterator<E> iterator()
// 返回此 set 中当前最后一个(最高)元素。
E last()
// 返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回 null。
E lower(E e)
// 获取并移除第一个(最低)元素;如果此 set 为空,则返回 null。
E pollFirst()
// 获取并移除最后一个(最高)元素;如果此 set 为空,则返回 null。
E pollLast()
// 如果此 set 中存在指定的元素,则将其移除。
boolean remove(Object o)
// 从此 set 中移除包含在指定 collection 中的所有元素。
boolean removeAll(Collection<?> c)
// 返回此 set 中的元素数目。
int size()
// 返回此 set 的部分视图,其元素范围从 fromElement 到 toElement。
NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)
// 返回此 set 的部分视图,其元素从 fromElement(包括)到 toElement(不包括)。
NavigableSet<E> subSet(E fromElement, E toElement)
// 返回此 set 的部分视图,其元素大于等于 fromElement。
NavigableSet<E> tailSet(E fromElement)
// 返回此 set 的部分视图,其元素大于(或等于,如果 inclusive 为 true)fromElement。
NavigableSet<E> tailSet(E fromElement, boolean inclusive)
复制代码

 

ConcurrentSkipListSet源码(JDK1.7.0_40版本)

ConcurrentSkipListSet.java的完整源码如下:

  View Code

ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,它的接口基本上都是通过调用ConcurrentSkipListMap接口来实现的。这里就不再对它的源码进行分析了。

 

ConcurrentSkipListSet示例

复制代码
 1 import java.util.*;
 2 import java.util.concurrent.*;
 3 
 4 /*
 5  *   ConcurrentSkipListSet是“线程安全”的集合,而TreeSet是非线程安全的。
 6  *
 7  *   下面是“多个线程同时操作并且遍历集合set”的示例
 8  *   (01) 当set是ConcurrentSkipListSet对象时,程序能正常运行。
 9  *   (02) 当set是TreeSet对象时,程序会产生ConcurrentModificationException异常。
10  *
11  * @author skywang
12  */
13 public class ConcurrentSkipListSetDemo1 {
14 
15     // TODO: set是TreeSet对象时,程序会出错。
16     //private static Set<String> set = new TreeSet<String>();
17     private static Set<String> set = new ConcurrentSkipListSet<String>();
18     public static void main(String[] args) {
19     
20         // 同时启动两个线程对set进行操作!
21         new MyThread("a").start();
22         new MyThread("b").start();
23     }
24 
25     private static void printAll() {
26         String value = null;
27         Iterator iter = set.iterator();
28         while(iter.hasNext()) {
29             value = (String)iter.next();
30             System.out.print(value+", ");
31         }
32         System.out.println();
33     }
34 
35     private static class MyThread extends Thread {
36         MyThread(String name) {
37             super(name);
38         }
39         @Override
40         public void run() {
41                 int i = 0;
42             while (i++ < 10) {
43                 // “线程名” + "序号"
44                 String val = Thread.currentThread().getName() + (i%6);
45                 set.add(val);
46                 // 通过“Iterator”遍历set。
47                 printAll();
48             }
49         }
50     }
51 }
复制代码

(某一次)运行结果

复制代码
a1, b1, 
a1, a1, a2, b1, 
b1, a1, a2, a3, b1,

a1, a2, a3, a1, a4, b1, b2, 
a2, a1, a2, a3, a4, a5, b1, b2, 
a3, a0, a4, a5, a1, b1, a2, b2, 
a3, a0, a4, a1, a5, a2, b1, a3, b2, a4, b3, 
a5, a0, b1, a1, b2, a2, b3, 
a3, a0, a4, a1, a5, a2, b1, a3, b2, a4, b3, a5, b4, 
b1, a0, b2, a1, b3, a2, b4, 
a3, a0, a4, a1, a5, a2, b1, a3, b2, a4, b3, a5, b4, b1, b5, 
b2, a0, a1, a2, a3, a4, a5, b3, b1, b4, b2, b5, 
b3, a0, b4, a1, b5, 
a2, a0, a3, a1, a4, a2, a5, a3, b0, a4, b1, a5, b2, b0, b3, b1, b4, b2, b5, b3, 
b4, a0, b5, 
a1, a2, a3, a4, a5, b0, b1, b2, b3, b4, b5, 
a0, a1, a2, a3, a4, a5, b0, b1, b2, b3, b4, b5, 
a0, a1, a2, a3, a4, a5, b0, b1, b2, b3, b4, b5, 
a0, a1, a2, a3, a4, a5, b0, b1, b2, b3, b4, b5, 
复制代码

结果说明
示例程序中,启动两个线程(线程a和线程b)分别对ConcurrentSkipListSet进行操作。以线程a而言,它会先获取“线程名”+“序号”,然后将该字符串添加到ConcurrentSkipListSet集合中;接着,遍历并输出集合中的全部元素。 线程b的操作和线程a一样,只不过线程b的名字和线程a的名字不同。
当set是ConcurrentSkipListSet对象时,程序能正常运行。如果将set改为TreeSet时,程序会产生ConcurrentModificationException异常。

<think>我们正在讨论Java并发包中的并发容器。根据引用[1]和[2],Java并发包提供了一系列线程安全的容器,用于解决多线程环境下的共享数据问题。这些容器可以分为几类:线程安全的List、Set、Map以及队列(队列已在之前的问题中详细讨论,这里主要关注非队列的容器)。 分类如下: 1. List: - CopyOnWriteArrayList: 基于写时复制技术,适用于读多写少的场景。 2. Set: - CopyOnWriteArraySet: 基于CopyOnWriteArrayList实现,同样适用于读多写少的场景。 - ConcurrentSkipListSet: 基于跳表实现的有序集合,支持并发访问。 3. Map: - ConcurrentHashMap: 高并发环境下使用的HashMap,通过分段锁(JDK7)或CAS+synchronized(JDK8)实现线程安全。 - ConcurrentSkipListMap: 基于跳表实现的有序Map,支持并发访问。 4. 队列(Queue/Deque): - 上一问题已详细讨论,包括阻塞队列、非阻塞队列等,这里不再重复。 下面我们详细说明每个容器的特点和使用场景。 一、List容器 1. CopyOnWriteArrayList - 原理:写操作(add, set, remove等)时,会复制底层数组,修改在新数组上进行,最后将新数组替换旧数组。读操作无锁,直接访问当前数组。 - 特点:读操作非常快,写操作因为需要复制数组,所以开销较大。适合读多写少的场景。 - 注意:由于读操作不会阻塞,所以可能读到旧数据,因此适合对数据一致性要求不高的场景。 - 示例代码: ```java CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); // 写操作(会复制数组) list.add("item1"); // 读操作(无锁) String item = list.get(0); ``` 二、Set容器 1. CopyOnWriteArraySet - 原理:内部使用CopyOnWriteArrayList实现,因此特性CopyOnWriteArrayList类似。 - 特点:同样适用于读多写少的场景,写操作开销大,读操作无锁。 - 注意:由于基于CopyOnWriteArrayList,所以它不适用于元素数量很大的集合,因为每次写操作都要复制整个数组。 2. ConcurrentSkipListSet - 原理:基于ConcurrentSkipListMap实现,使用跳表数据结构,保证元素的有序性(自然顺序或通过Comparator)。 - 特点:支持并发的插入、删除和查找操作,且操作的时间复杂度为$O(\log n)$。 - 适用场景:需要有序且并发的集合。 三、Map容器 1. ConcurrentHashMap - 原理:在JDK7中,使用分段锁(Segment)来减小锁的粒度,每个段独立加锁。在JDK8中,改为使用CAS+synchronized来保证并发安全,并且当链表长度超过阈值时转换为红黑树,以提高查找效率。 - 特点:高并发环境下性能优异,比Hashtable和Collections.synchronizedMap()性能好很多。 - 适用场景:高并发且需要键值对存储的场景。 - 示例代码: ```java ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("key1", 1); int value = map.get("key1"); ``` 2. ConcurrentSkipListMap - 原理:基于跳表(Skip List)实现的有序Map。跳表是一种可以快速查找、插入、删除的数据结构,通过多级索引实现。 - 特点:支持高并发的有序Map,所有操作都是线程安全的,且操作的时间复杂度为$O(\log n)$。 - 适用场景:需要有序且并发的键值对存储,例如排行榜等。 四、队列容器(简要提及,因为之前问题已覆盖) - 包括BlockingQueue的实现(如ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue等)和非阻塞队列(如ConcurrentLinkedQueue, ConcurrentLinkedDeque)等。 总结表格: | 容器类型 | 实现类 | 特点 | 适用场景 | |---------|--------|------|---------| | List | CopyOnWriteArrayList | 读无锁,写复制数组 | 读多写少,如监听器列表 | | Set | CopyOnWriteArraySet | 基于CopyOnWriteArrayList,读多写少 | 读多写少的集合 | | Set | ConcurrentSkipListSet | 基于跳表,有序,并发安全 | 需要有序的并发集合 | | Map | ConcurrentHashMap | 分段锁或CAS+synchronized,高并发 | 高并发键值存储,如缓存 | | Map | ConcurrentSkipListMap | 跳表实现,有序 | 需要有序的并发键值存储 | 注意:使用这些并发容器时,需要根据具体场景选择。例如,如果对读性能要求极高且写操作很少,可以选择CopyOnWrite系列;如果对并发写要求高,则选择ConcurrentHashMap或ConcurrentSkipListMap。 常见问题: 1. 如何选择List容器? - 如果需要频繁修改(写操作),则不建议使用CopyOnWriteArrayList,因为写操作会复制整个数组。此时,可以考虑使用Collections.synchronizedList(),但要注意其性能不如CopyOnWriteArrayList在读多写少场景下的表现。 2. ConcurrentHashMap在JDK7和JDK8中的区别? - JDK7:使用分段锁(Segment),每个段相当于一个小的HashMap,锁的粒度是段。 - JDK8:使用Node数组+链表+红黑树,锁的粒度是每个数组元素(桶),使用CAS和synchronized来保证并发安全。当链表长度大于8且数组长度超过64时,链表转换为红黑树。 3. 为什么需要ConcurrentSkipListMap? - 因为ConcurrentHashMap是无序的,当需要有序的Map时,可以使用ConcurrentSkipListMap。它提供了并发环境下的有序映射。 示例代码:ConcurrentHashMap的线程安全操作 ```java ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); // 多个线程可以安全地执行put操作 new Thread(() -> map.put("key2", 2)).start(); new Thread(() -> map.put("key3", 3)).start(); // 使用原子更新方法 map.computeIfAbsent("key4", k -> 4); ``` 最后,注意并发容器并不能保证所有复合操作都是原子性的,例如: ```java // 非原子操作 if (!map.containsKey("key")) { map.put("key", 1); } // 应该使用原子方法 map.putIfAbsent("key", 1); ``` 因此,使用并发容器时,应尽量使用其提供的原子方法(如putIfAbsent, compute, computeIfAbsent, merge等)来避免竞态条件。 相关问题: 1. CopyOnWriteArrayList的写操作是如何保证线程安全的? 2. ConcurrentHashMap在JDK8中是如何实现扩容的? 3. 跳表(Skip List)的数据结构是怎样的?为什么它适合并发? 4. 并发容器和普通容器通过Collections.synchronizedXXX包装的容器有什么区别?</think>### Java 并发包中的并发容器详解 Java 并发包(`java.util.concurrent`)提供了多种线程安全的容器类,用于解决多线程环境下的数据共享问题。这些容器通过高效的同步机制(如 CAS、写时复制、分段锁等)平衡线程安全性能。以下是主要分类及实现: --- #### 一、线程安全 List | 实现类 | 核心机制 | 特点 | 适用场景 | |-------------------------|----------------------|----------------------------------------------------------------------|------------------------------| | **`CopyOnWriteArrayList`** | 写时复制 (Copy-on-Write) | 写操作复制新数组,读操作无锁;读多写少时性能高,写操作开销大[^1] | 监听器列表、配置管理等低频写场景 | | **`Collections.synchronizedList`** | 互斥锁 | 包装普通`ArrayList`,所有方法用`synchronized`同步;简单但吞吐量低 | 兼容旧代码的简单同步需求 | **示例**: ```java // 高并发读场景 List<String> safeList = new CopyOnWriteArrayList<>(); safeList.add("data"); // 写时复制 String data = safeList.get(0); // 无锁读 // 简单同步 List<String> syncList = Collections.synchronizedList(new ArrayList<>()); ``` --- #### 二、线程安全 Set | 实现类 | 核心机制 | 特点 | 适用场景 | |-------------------------|----------------------|----------------------------------------------------------------------|------------------------------| | **`CopyOnWriteArraySet`** | 写时复制 | 基于`CopyOnWriteArrayList`实现,特性类似[^1] | 读多写少的集合操作 | | **`ConcurrentSkipListSet`** | 跳表 (Skip List) | 有序集合(自然序或`Comparator`),CAS无锁实现,高并发插入/删除 | 需排序的高并发集合(如排行榜)| | **`Collections.synchronizedSet`** | 互斥锁 | 包装普通`HashSet`,方法全同步 | 简单同步需求 | **示例**: ```java Set<Integer> leaderboard = new ConcurrentSkipListSet<>(); leaderboard.add(100); // 并发插入有序集合 ``` --- #### 三、线程安全 Map | 实现类 | 核心机制 | 特点 | 适用场景 | |-------------------------|----------------------|----------------------------------------------------------------------|------------------------------| | **`ConcurrentHashMap`** | 分段锁 + CAS (JDK7)<br>CAS + `synchronized` (JDK8+) | 高并发读写,锁粒度细;JDK8 引入红黑树优化哈希冲突 | 缓存、会话存储等高并发 K-V 场景 | | **`ConcurrentSkipListMap`** | 跳表 | 有序 Map,支持并发排序和范围查询 | 需排序的并发字典(如范围检索) | | **`Collections.synchronizedMap`** | 互斥锁 | 包装普通`HashMap`,全方法同步 | 简单同步需求 | **`ConcurrentHashMap` 关键优化**: - JDK8 取消分段锁,改用 `Node` + CAS + `synchronized` 锁单个桶(Bucket) - 哈希冲突时链表转红黑树(阈值:链表长度 ≥ 8) - 支持原子方法:`compute()`, `merge()`, `search()` 等[^1] **示例**: ```java ConcurrentHashMap<String, Integer> cache = new ConcurrentHashMap<>(); // 原子更新 cache.compute("key", (k, v) -> (v == null) ? 1 : v + 1); ``` --- #### 四、线程安全队列(Queue/Deque) > 详见上一问题总结,核心实现包括: > - **阻塞队列**:`ArrayBlockingQueue`, `LinkedBlockingQueue` > - **非阻塞队列**:`ConcurrentLinkedQueue`, `ConcurrentLinkedDeque` > - **特殊队列**:`DelayQueue`, `PriorityBlockingQueue`[^2][^3] --- ### 并发容器选择指南 | 需求场景 | 推荐容器 | |------------------------|-----------------------------------| | 高频读 + 低频写 | `CopyOnWriteArrayList`/`CopyOnWriteArraySet` | | 高并发 K-V 存储 | `ConcurrentHashMap` | | 有序集合 + 高并发 | `ConcurrentSkipListSet`/`ConcurrentSkipListMap` | | 生产者-消费者模型 | `ArrayBlockingQueue`(有界) | | 定时任务调度 | `DelayQueue` | --- ### 常见问题解决方案 #### ❌ 复合操作非原子性 ```java // 错误:contains + add 非原子 if (!set.contains(item)) { set.add(item); } // 正确:使用 ConcurrentHashMap 的原子方法 ConcurrentHashMap<String, Boolean> map = new ConcurrentHashMap<>(); map.putIfAbsent("key", true); ``` #### ❌ `CopyOnWriteArrayList` 写性能差 ```java // 避免频繁写操作:批量添加 List<String> batchData = Arrays.asList("a", "b", "c"); safeList.addAll(batchData); // 减少复制次数 ``` #### ❌ 遍历时并发修改 ```java // ConcurrentHashMap 迭代器弱一致性(不抛异常) ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.forEach((k, v) -> process(k, v)); // 安全遍历 ``` --- ### 总结 Java 并发容器的核心设计思想: 1. **降低锁粒度**:如 `ConcurrentHashMap` 的分段锁/CAS 优化。 2. **读写分离**:如 `CopyOnWriteArrayList` 写时复制。 3. **无锁算法**:如 `ConcurrentSkipListMap` 的跳表+CAS。 4. **专用场景优化**:如 `DelayQueue` 的延迟调度。 > **关键原则**: > - 优先选择并发专用容器(如 `ConcurrentHashMap`),而非 `Collections.synchronizedXXX` 包装类。 > - 根据读写比例选择数据结构(如读多写少用 `CopyOnWriteArrayList`)。 > - 有界性设计防止资源耗尽(如队列显式设置容量)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值