并发集合 CopyOnWrite

CopyOnWriteArrayList

内部结构比较简单;

包含了一个可重入锁:

 

transient final ReentrantLock lock = new ReentrantLock();

一个易变的对象类型数组。它是具体包含数据的主角,只是其内容总是在数据发生变化时,集体哗变:

 

private volatile transient Object[] array;

 

增删操作会使用Arrays.copyOf或者System.arraycopy,因此较ArrayList会慢。

 

查找很快,既是对内部数组的数据定位。较之ArrayList,没有校验边界,因为并发下,边界有可能发生改变。

 

public E get(int index) {
	return (E) (getArray()[index]);
}
 

CopyOnWriteArraySet

<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、付费专栏及课程。

余额充值