Java并发原生工具:并发容器
✅ 核心速览:
| 方案 | 示例 | 锁粒度 | 性能 | 适用场景 |
|---|---|---|---|---|
| 并发容器 | ConcurrentHashMap |
细粒度 / 无锁 | 高 | 高并发读写 |
| 同步包装器 | Collections.synchronizedMap() |
全局锁 | 中 | 低并发、简单场景 |
| 传统同步集合 | Hashtable, Vector |
全局锁 | 中 | 兼容旧代码 |
✅ 常见误区
-
ConcurrentHashMap.size()是实时精确的。
JDK 8+ 中size()是精确的(通过遍历所有桶计数),但高并发写时调用size()仍可能有短暂不一致,且性能开销大。建议:避免频繁调用size(),或使用mappingCount()(返回 long,更适用于大容量)。 -
ConcurrentHashMap允许 null。ConcurrentHashMap不允许 null,put(key, null)会抛出NullPointerException,这主要是为了避免二义性(get(key) == null无法区分“不存在”还是“值为 null”)。 -
ConcurrentLinkedQueue有size()方法?ConcurrentLinkedQueue.size()方法的时间复杂度是 O(n),因为它是无锁链表,需遍历计数。非阻塞队列应避免提供size(),因其在并发下意义不大。
并发容器是 java.util.concurrent 包中提供的一组线程安全的集合类,它们在多线程环境下可以安全地被多个线程并发读写,而无需外部加锁。相比对 Vector、Hashtable 或 Collections.synchronizedXxx() 的全局锁实现,并发容器通过更细粒度的锁(如分段锁)或无锁算法(如 CAS)大幅提升了并发性能。
它们的目标是:
- 允许多个线程同时读。
- 允许读与写在某些条件下并发进行。
- 在保证线程安全的前提下最大化吞吐量。
并发 Map
| 类 | 底层结构 | 线程安全机制 | 特点 |
|---|---|---|---|
ConcurrentHashMap |
分段哈希表 (JDK 7) CAS + synchronized锁桶 (JDK 8+) |
无锁读 + 细粒度写锁 | - 高并发读写首选 - 不允许 null key/value - 支持原子复合操作 |
ConcurrentSkipListMap |
跳表 (Skip List) | CAS + volatile | - 有序 Map - 并发性能优于 TreeMap |
1、ConcurrentHashMap 原理演进 (JDK 8+)
- JDK 7:使用分段锁(
Segment),将哈希表分成 16 段,每段独立加锁。 - JDK 8+:取消
Segment,直接对每个哈希桶(bin)的头节点加synchronized锁。- 读操作完全无锁(依赖
volatile字段保证可见性)。 - 写操作(如
put)只锁住目标桶,其他桶可并发访问。 - 高并发下性能显著优于
Hashtable或synchronizedMap。
- 读操作完全无锁(依赖
2、原子复合操作 (JDK 8+)
以下方法内部已处理竞争,无需外部同步。
// 线程安全:若 key 不存在,则计算并插入
map.computeIfAbsent("key", k -> expensiveComputation());
// 线程安全:仅当值等于 oldValue 时才更新
map.replace("key", oldValue, newValue);
// 线程安全:累加
map.computeIfPresent("counter", (k, v) -> v + 1);
并发 Queue / Deque
| 类 | 特性 | 适用场景 |
|---|---|---|
ConcurrentLinkedQueue |
无界、非阻塞、FIFO | 高吞吐、无界任务队列 |
ConcurrentLinkedDeque |
无界、非阻塞、双端队列 | 工作窃取(Work-Stealing)等 |
LinkedBlockingQueue |
有界/无界、阻塞、FIFO | 线程池任务队列(如 Executors.newFixedThreadPool) |
ArrayBlockingQueue |
有界、阻塞、FIFO | 固定容量缓冲区 |
SynchronousQueue |
无容量、直接传递 | 生产者-消费者直接交接(如 Executors.newCachedThreadPool) |
PriorityBlockingQueue |
无界、阻塞、优先级队列 | 按优先级处理任务 |
⚠️ “并发” ≠ “阻塞”:
ConcurrentLinkedQueue是非阻塞的(失败即返回,不等待)。BlockingQueue实现是阻塞的(如put()在队列满时会等待)。
并发 Set
Java 没有直接提供 ConcurrentHashSet,但可以通过 ConcurrentHashMap.newKeySet() 来创建。其内部基于 ConcurrentHashMap(key 为元素,value 为 Boolean.TRUE),因此拥有 ConcurrentHashMap 所有的并发优势。
Copy-On-Write 容器
| 类 | 机制 | 适用场景 | 特点 |
|---|---|---|---|
CopyOnWriteArrayList |
写时复制(每次修改都创建新数组) | 读多写少(如监听器列表) | - 读操作无锁、高性能。 - 写操作昂贵(需复制整个数组)。 - 弱一致性:读线程可能看不到最新写入。 |
CopyOnWriteArraySet |
基于 CopyOnWriteArrayList |
读多写少(如监听器列表) | 同上 |
使用
CopyOnWriteArrayList注意事项:
- 仅适用于写操作非常少的场景,且能容忍读写的短暂不一致。
- 迭代器是只读的,不支持增删改。因为它遍历的是一个快照。
最佳实践
- Map 场景首选
ConcurrentHashMap。 - 队列场景,如果需要阻塞使用
BlockingQueue(如LinkedBlockingQueue),如果追求极致吞吐且无界可以使用ConcurrentLinkedQueue。 - Set 场景可以通过
ConcurrentHashMap.newKeySet()来构造。 - 读多写少的静态数据场景可以选择使用
CopyOnWriteArrayList。 - 尽量使用并发容器自带的复合操作 API(如
ConcurrentHashMap.putIfAbsent),不要自行操作(如if (!set.contains(x)) set.add(x))。
JDK 21 中的新特性与演进
ConcurrentHashMap在 JDK 16+ 进一步优化了并发度和内存布局。- 虚拟线程(Virtual Threads):所有并发容器均可安全用于虚拟线程环境,无特殊适配需求。
- JDK 15+ 中 JVM 移除偏向锁,对
ConcurrentHashMap的synchronized桶锁影响极小,因其锁竞争本就低。
更多推荐
所有评论(0)