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

JDK Concurrent Collections 设计及工作原理与教程

这是关于 JDKjava.util.concurrent包下并发集合(Concurrent Collections)的设计原理、核心机制与实战教程。

内容会从设计哲学深入到具体实现,并附带代码示例。


第一部分:设计哲学与核心概念

并发集合的设计目标是在多线程环境下提供比synchronized包装类(如Collections.synchronizedMap)更高的并发性

1.1 核心设计原则
  • 细粒度锁(Fine-Grained Locking):不对整个数据结构加锁,而是对数据的一部分(如桶、节点)加锁,允许不同线程操作不同部分。

  • 无锁(Lock-Free)与乐观锁:利用 CAS(Compare-And-Swap)CPU 原子指令,避免线程阻塞(如ConcurrentLinkedQueue)。

  • 写时复制(Copy-On-Write):修改时复制一份新数据,读操作永远无需加锁(如CopyOnWriteArrayList)。

  • 弱一致性(Weakly Consistent)迭代器:迭代器不抛出ConcurrentModificationException,且能反映迭代开始时的部分状态,但未必是最新状态。

1.2 与遗留同步容器的对比
特性同步容器(Hashtable, Vector)并发集合(ConcurrentHashMap)
锁粒度整个对象锁分段锁 / 节点锁
并发度低,同一时刻仅一个线程操作高,多线程可并发读写不同段
迭代器快速失败(Fail-Fast)弱一致,不抛异常
null键/值不允许(Hashtable)ConcurrentHashMap不允许null

第二部分:核心并发集合工作原理详解

2.1 ConcurrentHashMap(重点)

设计演进

  • JDK 1.7:分段锁(Segment 数组,每个 Segment 继承 ReentrantLock)。

  • JDK 1.8+:废除分段锁,采用CAS + synchronized锁定链表/红黑树的头节点。

核心机制

  1. 初始化:延迟初始化,首次put时通过 CAS 创建数组。

  2. 插入(put)流程

    • 若桶为空:CAS 尝试插入新节点。

    • 若桶不为空:synchronized锁定桶的头节点,进行链表遍历或红黑树插入。

  3. 扩容(transfer)

    • 支持多线程并发扩容(每个线程负责一部分桶的迁移)。

    • 使用sizeCtl控制状态(负数表示正在扩容)。

  4. 计数(size/mappingCount):使用 LongAdder 风格的CounterCell数组分散计数,减少竞争。

注意ConcurrentHashMap不允许 key/value 为 null,因为这会混淆“未找到”和“值为 null”的场景(在并发下无法用containsKey准确判断)。

2.2 BlockingQueue 实现类(生产者-消费者模型)
  • ArrayBlockingQueue:有界,内部使用单锁(ReentrantLock)控制入队出队,支持公平/非公平。

  • LinkedBlockingQueue:可选有界,内部使用双锁(takeLock + putLock)分离读写,吞吐量更高。

  • SynchronousQueue:无容量,每个插入必须等待一个提取,常用于线程池(Executors.newCachedThreadPool)。

  • PriorityBlockingQueue:无界,基于优先堆,元素需实现 Comparable。

工作原理:利用ConditionnotEmpty/notFull)实现阻塞等待。当队列满时,put线程阻塞;当队列空时,take线程阻塞。

2.3 CopyOnWriteArrayList / CopyOnWriteArraySet
  • 适用场景:读多写极少(如黑名单、监听器列表)。

  • 原理:执行写操作(add/remove/set)时,复制底层数组,修改后重新赋值给 volatile 引用。

  • 优点:读操作无需锁,性能极高。

  • 缺点:写操作开销大(复制成本),数据强一致性延迟高。

2.4 ConcurrentLinkedQueue
  • 无界非阻塞队列,基于Michael-Scott 无锁算法

  • 核心:使用 CAS 更新头尾节点(headtail不一定指向真正的首尾,存在滞后性)。

  • 迭代器:弱一致性,遍历时若节点被删除,跳过即可。

2.5 ConcurrentSkipListMap / SkipListSet
  • 有序并发集合,基于跳表(Skip List)结构。

  • 优势:提供 O(log n) 的并发访问,避免红黑树复杂的重平衡操作。

  • 用途:需要并发有序 Map 时(如替代TreeMap+Collections.synchronized)。


第三部分:实战教程与代码示例

3.1 高性能缓存场景(使用 ConcurrentHashMap)

模拟一个简单的线程安全缓存,支持超时失效(简化版)。

import java.util.concurrent.*; public class ConcurrentCache<K, V> { private final ConcurrentHashMap<K, Future<V>> cache = new ConcurrentHashMap<>(); private final ConcurrentHashMap<K, Long> expireMap = new ConcurrentHashMap<>(); private final long ttlMillis; public ConcurrentCache(long ttlMillis) { this.ttlMillis = ttlMillis; } public V get(K key, Callable<V> loader) throws Exception { long now = System.currentTimeMillis(); // 检查过期 Long expireTime = expireMap.get(key); if (expireTime != null && now > expireTime) { cache.remove(key); expireMap.remove(key); } Future<V> future = cache.get(key); if (future == null) { FutureTask<V> task = new FutureTask<>(loader); Future<V> existing = cache.putIfAbsent(key, task); if (existing == null) { future = task; task.run(); // 执行加载 expireMap.put(key, now + ttlMillis); } else { future = existing; } } return future.get(); } }
3.2 生产者-消费者(使用 LinkedBlockingQueue)
public class ProducerConsumerDemo { public static void main(String[] args) { BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10); Runnable producer = () -> { try { for (int i = 0; i < 20; i++) { queue.put(i); // 阻塞直到有空间 System.out.println("Produced: " + i); } } catch (InterruptedException ignored) {} }; Runnable consumer = () -> { try { while (true) { Integer val = queue.take(); // 阻塞直到有元素 System.out.println("Consumed: " + val); Thread.sleep(100); } } catch (InterruptedException ignored) {} }; new Thread(producer).start(); new Thread(consumer).start(); new Thread(consumer).start(); // 多消费者 } }
3.3 读多写少场景(使用 CopyOnWriteArrayList)
public class BlacklistService { // 黑名单列表,读操作远多于写操作 private final CopyOnWriteArrayList<String> blacklist = new CopyOnWriteArrayList<>(); public boolean isBlacklisted(String ip) { return blacklist.contains(ip); // 无锁,极快 } public void addToBlacklist(String ip) { blacklist.add(ip); // 复制数组,开销大 } public void removeFromBlacklist(String ip) { blacklist.remove(ip); } }

第四部分:常见面试题与调优建议

4.1 常见面试问题

Q1:ConcurrentHashMap 在 JDK 1.8 中为什么放弃分段锁?

  • 分段锁占用内存多(每个 Segment 继承 ReentrantLock)。

  • 分段锁在扩容时仅锁住当前 Segment,但全局统计 size 需要获取所有锁,效率不高。

  • 使用 CAS + synchronized 锁头节点,锁粒度更细(仅锁住单个桶),支持更高并发。

Q2:ConcurrentHashMap 如何保证线程安全?

  • 初始化数组:CAS 保证单次初始化。

  • 写操作:CAS 插入空桶,非空桶用 synchronized 锁头节点。

  • 扩容:多线程协同迁移,每个线程使用 CAS 抢占任务。

Q3:ConcurrentLinkedQueue 的 size() 方法为何不准确?

  • 因为在并发环境下,size 需要遍历全队列,且遍历过程中队列可能变化,因此返回的是弱一致性的近似值。

4.2 性能调优建议
场景推荐集合注意事项
高频读写 MapConcurrentHashMap初始容量估算准确,避免频繁扩容。
生产者-消费者LinkedBlockingQueue若消费者慢于生产者,防止 OOM(设置边界)。
大量读、极少写 ListCopyOnWriteArrayList避免频繁写入,否则复制成本巨大。
并发排序/范围查询ConcurrentSkipListMap比 ConcurrentHashMap 有序,但吞吐量略低。
高并发计数器LongAdder(非集合)AtomicLong更适合累积统计。
4.3 常见陷阱
  • 误用ConcurrentHashMap复合操作非原子if (!map.containsKey(k)) map.put(k,v)不是原子操作,应改用putIfAbsentcomputeIfAbsent

  • 阻塞队列的drainTo()在批量取数据时效率远高于循环take()

  • 不要将null值放入ConcurrentHashMap,否则可能 NPE。


第五部分:总结思维导图(文字版)

JDK 并发集合 ├── 设计哲学 │ ├── 细粒度锁 │ ├── CAS无锁 │ ├── Copy-On-Write │ └── 弱一致性迭代器 ├── Map体系 │ ├── ConcurrentHashMap(CAS+synchronized,无序) │ └── ConcurrentSkipListMap(跳表,有序) ├── List/Set体系 │ ├── CopyOnWriteArrayList/Set(读多写少) │ └── 无ConcurrentArrayList(自行Collections.synchronized) ├── Queue/Deque体系 │ ├── ConcurrentLinkedQueue(无界非阻塞) │ ├── LinkedBlockingQueue(有界双锁阻塞) │ ├── ArrayBlockingQueue(有界单锁阻塞) │ └── SynchronousQueue(0容量传递) └── 工具类辅助 ├── LongAdder(高并发计数) └── CompletableFuture(异步编排)
http://www.jsqmd.com/news/1140413/

相关文章:

  • AI视觉项目实战指南:从PyTorch环境搭建到模型部署全流程
  • codebase-memory-mcp是什么?AI代码库记忆工具部署与使用指南
  • 冬青先令规格选择,设计尺寸要看哪些细节
  • 想要学习deepseekGEO技术?深圳这些优质靠谱课程值得打算入行的你了解
  • m4s-converter解决方案:B站缓存视频格式转换与个人备份工具
  • Hermes Agent部署指南:从环境准备到生产环境配置
  • CK2DLL双字节补丁:三步实现十字军之王II完美中文显示
  • ComfyUI单图生成3D模型:腾讯混元3D工作流配置与实践
  • IBM Condor 1121量子比特芯片解析:超导电路如何实现千比特级集成
  • B站m4s视频转换完全指南:3分钟解锁跨平台播放自由
  • 【ROS2】 Ubuntu 避坑指南:PyCharm 桌面快捷方式与 Conda 环境配置全记录
  • 懒人程序员的信息推送革命
  • 如何快速部署Skyline APIServer?Ubuntu与RHEL系统的终极安装教程
  • 【日记】看了那个小女孩的表演(2239字)
  • 英雄联盟自动化工具箱:3大核心功能提升游戏效率300%
  • UnityExplorer自由视角相机终极指南:突破游戏视角限制的完整教程
  • Jetty 9.4.40 前 ConcatServlet 漏洞实战:双重URL编码绕过与WEB-INF文件泄露
  • 终极指南:如何用OpenRocket免费软件设计你的第一枚模型火箭 [特殊字符]
  • WayCa鲲鹏高性能调度技术深度剖析:AMU、BTI、SVE等特性实战指南
  • 端到端加密项目 KaleidoTalk V3.0:你的聊天记录,只有你能看见
  • 如何在5分钟内开始使用UE4SS游戏脚本系统创建你的第一个游戏Mod
  • 如何在5分钟内掌握UE4SS:Unreal Engine游戏修改终极指南
  • 163MusicLyrics:免费歌词下载工具,轻松获取网易云和QQ音乐歌词
  • Visual C++运行库10分钟终极修复指南:告别DLL错误的一键解决方案
  • 终极指南:如何使用DDrawCompat让经典DirectX游戏在现代Windows系统上流畅运行
  • GEO Labs 半年观察 · 2026 上半年:算法收敛、平台分化与服务商格局
  • DistroAV技术解析:构建基于NDI协议的OBS Studio网络音视频生态系统
  • 开源AIOps框架对比:Metis、KeenTune与AI4Ops在真实运维场景的横向评测
  • “A Deep Probabilistic Model for Customer Lifetime Value Prediction“ 论文笔记
  • 孤能子视角:扩散模型与EIS关系场耦合