并发容器(Collections)
一、并发安全问题根源
1. List(如ArrayList)
- 问题表现:多线程同时调用
add
、remove
等方法时,可能抛出ConcurrentModificationException
或导致数据不一致。 - 根本原因:
- 非原子性操作:如
add
操作的流程(检查容量→扩容→插入)在多线程下可能被中断。 - 结构性修改冲突:多线程同时修改集合结构(如扩容)导致内部数组索引混乱。
- 非原子性操作:如
2. Set(如HashSet)
- 问题表现:HashSet底层基于HashMap,多线程插入可能丢失数据或触发异常。
- 根本原因:
- HashMap的线程不安全:HashSet依赖的HashMap在多线程下可能因哈希冲突或扩容导致死循环或数据丢失。
3. Map(如HashMap)
- 问题表现:多线程并发插入或扩容时,可能造成死循环(JDK 1.7)或数据覆盖(JDK 1.8+)。
- 根本原因:
- 哈希表扩容冲突:多线程同时触发扩容(
resize
)时,链表或红黑树重构过程中节点引用混乱。 - 非原子性put操作:多线程同时插入同一哈希桶可能导致数据覆盖。
- 哈希表扩容冲突:多线程同时触发扩容(
二、解决方案
1. List的线程安全实现
-
Collections.synchronizedList
通过同步方法包装普通List,保证原子性但性能较低:List<String> list = Collections.synchronizedList(new ArrayList<>());
-
CopyOnWriteArrayList(推荐)
写操作复制新数组,读操作无锁,适合读多写少场景:List<String> list = new CopyOnWriteArrayList<>();
2. Set的线程安全实现
-
Collections.synchronizedSet
同步方法包装普通Set:Set<String> set = Collections.synchronizedSet(new HashSet<>());
-
CopyOnWriteArraySet(推荐)
基于CopyOnWriteArrayList
实现,适合元素少、读多写少:Set<String> set = new CopyOnWriteArraySet<>();
3. Map的线程安全实现
-
ConcurrentHashMap(推荐)
- JDK 1.7:分段锁(Segment),降低锁粒度。
- JDK 1.8+:CAS + synchronized锁单个哈希桶,并发性能更高。
Map<String, String> map = new ConcurrentHashMap<>();
-
Collections.synchronizedMap
同步方法包装普通Map,适用于低并发:Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
三、对比与选型
集合类型 | 非安全类 | 安全实现方案 | 适用场景 |
---|---|---|---|
List | ArrayList | CopyOnWriteArrayList | 读多写少(如配置信息) |
Collections.synchronizedList | 简单同步需求 | ||
Set | HashSet | CopyOnWriteArraySet | 元素少、读多写少 |
Collections.synchronizedSet | 低并发场景 | ||
Map | HashMap | ConcurrentHashMap | 高并发读写(首选) |
Collections.synchronizedMap | 兼容旧代码或低并发需求 |
四、注意事项
- 性能权衡:
CopyOnWriteArrayList
每次写操作复制数组,写频繁时性能差。ConcurrentHashMap
在JDK 1.8+中优化为CAS+锁,性能接近无锁。
- 避免误区:
Vector
和Hashtable
通过全表锁实现安全,但高并发下性能差,不推荐使用。
- 开发建议:
- 默认使用
ConcurrentHashMap
替代HashMap
,CopyOnWriteArrayList
替代ArrayList
。 - 若需强一致性(如金融场景),需结合显式锁(如
ReentrantLock
)或数据库事务。
- 默认使用
通过选择适合的并发容器,可避免ConcurrentModificationException
、数据丢失等问题,同时平衡性能与线程安全。