Java 并发集合
并发集合解决什么、不解决什么:ConcurrentHashMap、阻塞队列与写时复制结构的选型。
#concept
#growing
#dev
#java
[!info] related notes
Java 并发集合
这个概念为什么出现
普通 HashMap/ArrayList 不支持无同步的并发修改;安全发布后只读访问是不同场景。并发集合在特定操作上提供线程安全与更好伸缩,但并不自动保证多步业务不变量。
[!abstract] 一句话理解 并发集合按各自 API 提供线程安全;并非每次批量调用都是整体原子操作。复合“先查后改”需原子方法(如 compute)或所有相关访问共同遵守的同步协议。
最小可运行示例
场景:多线程统计接口命中次数
import java.util.concurrent.ConcurrentHashMap;
public class HitCounter {
private final ConcurrentHashMap<String, Integer> hits = new ConcurrentHashMap<>();
void hit(String path) {
hits.merge(path, 1, Integer::sum); // 原子复合更新
}
int get(String path) {
return hits.getOrDefault(path, 0);
}
public static void main(String[] args) throws Exception {
HitCounter c = new HitCounter();
Thread t1 = new Thread(() -> { for (int i=0;i<1000;i++) c.hit("/a"); });
Thread t2 = new Thread(() -> { for (int i=0;i<1000;i++) c.hit("/a"); });
t1.start(); t2.start(); t1.join(); t2.join();
System.out.println(c.get("/a"));
}
}
建议运行:
javac HitCounter.java && java HitCounter
期望输出:
2000
结合场景再看三个关注点
- 用
merge/compute而不是 get+put。 - ConcurrentHashMap 的迭代弱一致,不保证整个映射的快照;CopyOnWriteArrayList 的迭代器则固定于创建时的数组快照。\
- BlockingQueue 适合作生产者-消费者通道。
核心概念与准确模型
1. ConcurrentHashMap
- 以每个键的原子操作与 API 可见性保证推理;分段/桶级同步是随 JDK 演进的实现细节。
- compute/merge 的回调应短小,避免慢 I/O 或递归修改该映射;存入的可变对象不会自动变成线程安全。\
- 禁止 null 键值(与 HashMap 不同)
2. BlockingQueue
- put/take 必要时等待;offer 返回是否入队,add 在有界队列满时抛异常;限时 offer 可实现有界等待。
- 接口本身没有通用 close,消费者退出需中断、结束标记或其他协议。\
- 线程池工作队列常用
3. CopyOnWriteArrayList
- 读多写少
- 写拷贝成本高;快照迭代器不支持 remove/set/add,也看不到创建后的修改。\
边界情况与反直觉行为
- size/isEmpty 等只能作为并发变化中的观察,不应据此完成“检查后执行”的业务控制;putAll 等批量操作也未必整体原子。\
- 复合业务校验库存仍要领域层原子性/DB 约束。
- 并发集合 ≠ 免锁设计完成。
常见误区
[!warning] 常见误区:ConcurrentHashMap 包治百病 它不替代事务与跨键不变量。
工程实践
- 优先原子复合 API。
- 队列设定容量实现背压。
- 读多写少结构先测量。
- 与数据库约束一起设计正确性。
本节总结
- 并发容器保单操作安全
- 复合操作要用原子 API
- 选型看读写模式
自测题
- 为什么 CHM 不支持 null?
- BlockingQueue 如何形成背压?
参考答案
- null 与“不存在”语义混淆,并发下更难解释。
- 满时 put 等待,offer 可返回 false 或限时等待;调用方必须实际阻塞、限流或处理拒绝,单有一个有界队列不会自动完成背压设计。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| ConcurrentHashMap | API | 语义 |
| BlockingQueue | API | 阻塞队列 |