当前位置: 首页 > news >正文

JUC核心解析系列(三)——并发容器深度解析

一、为什么需要并发容器?

传统的集合(如 ArrayListHashMap)在多线程环境下存在竞态条件问题,通过 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 (非线程安全)6852
SynchronizedMap1,420938
ConcurrentHashMap21387

结论:JDK提供的并发容器通过 细粒度锁无锁算法 实现性能飞跃


二、核心并发容器分类及实现原理
容器名称线程安全原理适用场景特性
ConcurrentHashMap分段锁 (JDK7) / CAS + synchronized (JDK8+)高并发键值存储JDK8+ 使用 Node 数组 + 链表/红黑树,锁粒度细化到桶级别
ConcurrentSkipListMap跳表 + CAS需排序的并发 Map自然有序,支持范围查询
CopyOnWriteArrayList写时复制读多写少的场景写操作复制新数组,读操作无锁
CopyOnWriteArraySet基于 CopyOnWriteArrayList读多写少的集合内部使用 CopyOnWriteArrayList 实现
ConcurrentLinkedQueueCAS 无锁算法高并发队列非阻塞无界队列,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无缓存直接传递任务(生产者必须等待消费者)
DelayQueuePriorityQueue 封装元素按延迟时间出队(用于定时任务)

典型使用(生产者-消费者):

// 创建有界队列(容量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 (非线程安全)SynchronizedMapConcurrentHashMap (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);}
}

六、选型决策树
极低
中/高
需线程安全集合
写操作频率
CopyOnWriteArrayList/Set
是否需要排序
ConcurrentSkipListMap/Set
是否阻塞
BlockingQueue实现类
ConcurrentHashMap
七、总结最佳实践

并发容器通过 减少锁竞争无锁算法写时复制 等机制,在高并发场景中显著提升性能。核心在于:

  • 锁粒度优化(如 ConcurrentHashMap 的分段锁/桶锁)
  • 读多写少时分离读写(如 CopyOnWriteArrayList
  • 阻塞队列解决生产者-消费者问题
  • 跳表实现高效有序并发访问

选择准则: 根据具体场景(读写比例、是否需要排序、是否阻塞)选择最匹配的并发容器:

  1. 高并发Map必选ConcurrentHashMap (JDK8+)
  2. 注册监听器CopyOnWriteArrayList
  3. 数据传输管道:根据场景选ArrayBlockingQueue(固定容量)或LinkedBlockingQueue(高吞吐)
  4. 分布式锁排队SynchronousQueue实现直接传递
  5. 定时任务调度DelayQueue实现延迟执行

性能压测数据表明:并发容器在16核服务器上比同步集合吞吐量提升6-8倍

欢迎在评论区交流实际应用中的并发问题! 🚀

相关文章:

  • acm模式stringstream
  • Windows平台进程加速方案研究:以网盘下载优化为例
  • Relin梦中门——第二章——感官
  • 带中断计数器的UART接收中断程序 (8259@400H/402H)
  • FreeRTOS定时器
  • Element Plus 去除下拉菜单周黑边
  • Ant Design 版本演进详解:从 1.x 到 5.x 的发展历程
  • docker安装mysql数据库及简单使用
  • 自恢复式保险丝如何实现自恢复?
  • 走线宽度对高频插入损耗的影响
  • Blender——建构、粒子、灯光、动画
  • TensorFlow Serving学习笔记2: 模型服务
  • Linux下成功编译CPU版Caffe的保姆级教程(基于Anaconda Python3.8 包含完整可用Makefile.config文件)
  • 使用预训练卷积神经模型进行分类(MATLAB例)
  • 【读点论文】A Survey on Open-Set Image Recognition
  • JavaScript基础-事件委托(代理、委派)
  • 【系统分析师】2009年真题:案例分析-答案及详解
  • FreeRTOS任务相关API简介
  • KJY0047-J1阶段测试
  • 【游资悟道】陈小群成长历史与股市悟道心法
  • 成年男女做羞羞视频网站/东莞网络公司电话
  • 南京网站制作步骤/怎么推广
  • 南昌做网站kaiu/陕西网页设计
  • 做网站的zk啥/汕头seo优化培训
  • 高安高端网站设计公司/郑州抖音seo
  • 响应式网站模板下载免费/专业网站快速