JUC核心解析系列(三)——并发容器深度解析
一、为什么需要并发容器?
传统的集合(如 ArrayList
、HashMap
)在多线程环境下存在竞态条件问题,通过 Collections.synchronizedXXX()
包装的同步集合虽然线程安全,但采用全局锁机制导致性能低下。Java 5+ 的 java.util.concurrent
包提供了更高效的并发容器,它们实现了细粒度锁、无锁算法或 CAS(Compare-And-Swap) 操作,显著提升了并发性能。
// 同步集合的致命缺陷
Map<String, Object> syncMap = Collections.synchronizedMap(new HashMap<>());
// 所有操作串行执行!put()/get()都要抢同一把锁
📊 性能对比测试(16线程操作100万次):
容器类型 | 写操作耗时(ms) | 读操作耗时(ms) |
---|---|---|
HashMap (非线程安全) | 68 | 52 |
SynchronizedMap | 1,420 | 938 |
ConcurrentHashMap | 213 | 87 |
结论:JDK提供的并发容器通过 细粒度锁 和 无锁算法 实现性能飞跃!
二、核心并发容器分类及实现原理
容器名称 | 线程安全原理 | 适用场景 | 特性 |
---|---|---|---|
ConcurrentHashMap | 分段锁 (JDK7) / CAS + synchronized (JDK8+) | 高并发键值存储 | JDK8+ 使用 Node 数组 + 链表/红黑树,锁粒度细化到桶级别 |
ConcurrentSkipListMap | 跳表 + CAS | 需排序的并发 Map | 自然有序,支持范围查询 |
CopyOnWriteArrayList | 写时复制 | 读多写少的场景 | 写操作复制新数组,读操作无锁 |
CopyOnWriteArraySet | 基于 CopyOnWriteArrayList | 读多写少的集合 | 内部使用 CopyOnWriteArrayList 实现 |
ConcurrentLinkedQueue | CAS 无锁算法 | 高并发队列 | 非阻塞无界队列,FIFO |
BlockingQueue 及其实现 | 锁 + Condition 等待队列 | 生产者-消费者模型 | 提供阻塞的 put() /take() 方法 |
三、重点容器详解
1. ConcurrentHashMap
JDK7 分段锁实现:
将数据分成 Segment
数组(默认16段),每段独立加锁,不同段可并发写。
final Segment<K,V>[] segments; // 分段锁数组
JDK8+ 革命性升级:抛弃分段锁,采用 CAS + synchronized 桶级别锁
final V putVal(K key, V value) {Node<K,V>[] tab; int n, i;// 无锁化初始化if ((tab = table) == null || (n = tab.length) == 0)tab = initTable();// 定位桶位置(无锁读)else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// CAS写入空桶if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))break; }else {synchronized (f) { // 锁住桶头节点// 链表插入或树化逻辑if (tabAt(tab, i) == f) {if (fh >= 0) {// 链表操作...}else if (f instanceof TreeBin) {// 红黑树操作...}}}}
}
关键优化点:
-
✅ 桶位锁:仅锁冲突桶,非冲突操作并行执行
-
✅ 无锁读:Node的val和next用volatile修饰
-
✅ 计数器分离:size()通过
baseCount
+CounterCell[]
分段统计 -
关键方法:
putVal()
:通过 CAS + synchronized 插入数据get()
:无锁读(利用 volatile 内存可见性)size()
:通过baseCount
+CounterCell[]
分段统计
2. CopyOnWriteArrayList
写时复制(COW)机制:
public boolean add(E e) {synchronized (lock) { // 写操作加锁Object[] es = getArray();int len = es.length;// 复制新数组(内存开销来源)es = Arrays.copyOf(es, len + 1);es[len] = e; // 修改新数组setArray(es); // volatile写替换引用return true;}
}
// 读操作完全无锁!
public E get(int index) {return elementAt(getArray(), index);
}
-
读操作直接访问原数组(无需锁)
-
写操作复制新数组并替换引用
-
缺点:
- 内存占用高(写操作需复制整个数组)
- 数据弱一致性(读操作可能读到旧数据)
-
适用场景:
- 🔥 监听器列表(如Spring事件机制)
- 🔥 白名单/黑名单等低频更新配置
🚨 警告:每次add()需复制整个数组!1万次写入=1GB内存开销!
3. BlockingQueue 阻塞队列
实现类 | 数据结构 | 特点 |
---|---|---|
ArrayBlockingQueue | 数组 + 单锁 | 有界队列,固定容量,公平锁可选 |
LinkedBlockingQueue | 链表 + 双锁 | 默认无界(可设上限),吞吐量高 |
PriorityBlockingQueue | 数组 + 二叉堆 | 支持优先级排序的无界队列 |
SynchronousQueue | 无缓存 | 直接传递任务(生产者必须等待消费者) |
DelayQueue | PriorityQueue 封装 | 元素按延迟时间出队(用于定时任务) |
典型使用(生产者-消费者):
// 创建有界队列(容量100)
BlockingQueue<Task> queue = new ArrayBlockingQueue<>(100); // 生产者线程
Runnable producer = () -> {while (true) {Task task = createTask();queue.put(task); // 队列满时自动阻塞!}
};// 消费者线程
Runnable consumer = () -> {while (true) {Task task = queue.take(); // 队列空时自动阻塞!processTask(task);}
};
四、性能对比(关键指标)
操作 | HashMap (非线程安全) | SynchronizedMap | ConcurrentHashMap (JDK8) |
---|---|---|---|
读操作 (10线程) | 最快 | 慢(全局锁) | 接近无锁 |
写操作 (10线程) | 崩溃(竞态条件) | 极慢(串行化) | 高吞吐(锁分段/CAS) |
迭代器 | 弱一致性 | 强一致性(锁全表) | 弱一致性(安全) |
结论: 并发场景下优先选
ConcurrentHashMap
,读多写少用CopyOnWriteArrayList
,生产者消费者用BlockingQueue
。
五、使用注意事项
5.1 弱一致性迭代器:
ConcurrentHashMap
的迭代器可能反映创建后的修改,但不保证实时一致性。
🚫 陷阱:忽视弱一致性
ConcurrentHashMap<String, String> map = ...;
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {// 迭代过程中map修改不会抛异常!// 但可能读到过时数据System.out.println(it.next());
}
应对策略:
- 需要强一致性时使用
Collections.synchronizedMap()
- 业务层面容忍短暂数据不一致
5.2 合理选择容器:
- 读远大于写:
CopyOnWriteArrayList
- 需排序:
ConcurrentSkipListMap
- 精准阻塞控制:
ArrayBlockingQueue
5.3 避免复合操作:
即使单个方法线程安全,组合操作仍需要额外同步:
// 错误!非原子操作
if (!map.containsKey(key)) {map.put(key, value);
}
正确做法:
// 使用原子方法
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.computeIfAbsent("key", k -> 1); // 或显示加锁
synchronized (map) {if (!map.containsKey(key)) {map.put(key, value);}
}
六、选型决策树
七、总结最佳实践
并发容器通过 减少锁竞争、无锁算法 和 写时复制 等机制,在高并发场景中显著提升性能。核心在于:
- 锁粒度优化(如
ConcurrentHashMap
的分段锁/桶锁) - 读多写少时分离读写(如
CopyOnWriteArrayList
) - 阻塞队列解决生产者-消费者问题
- 跳表实现高效有序并发访问
选择准则: 根据具体场景(读写比例、是否需要排序、是否阻塞)选择最匹配的并发容器:
- 高并发Map必选:
ConcurrentHashMap
(JDK8+) - 注册监听器:
CopyOnWriteArrayList
- 数据传输管道:根据场景选
ArrayBlockingQueue
(固定容量)或LinkedBlockingQueue
(高吞吐) - 分布式锁排队:
SynchronousQueue
实现直接传递 - 定时任务调度:
DelayQueue
实现延迟执行
性能压测数据表明:并发容器在16核服务器上比同步集合吞吐量提升6-8倍!
欢迎在评论区交流实际应用中的并发问题! 🚀