JUC包下常用的并发容器速览

注意
本文最后更新于 2024-05-06,文中内容可能已过时。

前言

普通集合(ArrayList、HashMap)全部线程不安全。JUC包下并发容器,专门解决多线程并发读写安全问题。

JUC容器分为四大类:

  1. 并发Map:ConcurrentHashMap、ConcurrentSkipListMap

  2. 并发List/Set:CopyOnWriteArrayList、CopyOnWriteArraySet

  3. 阻塞队列(BlockingQueue):七大常用阻塞队列(线程池核心)

  4. 无锁并发队列:ConcurrentLinkedQueue

一、ConcurrentHashMap 🔴

1.1 整体定位

线程安全的键值对集合,替代 Hashtable。Hashtable 是全局锁、并发极差;ConcurrentHashMap 锁粒度极小,并发性能极高。

1.2 JDK1.7 底层原理

数据结构:数组 + 链表 + 分段锁 Segment

  • 将整个Map分为16个Segment分段

  • 每个Segment独立持有一把ReentrantLock

  • 不同Segment的读写互不影响,大幅提升并发

缺点:锁粒度依旧偏大、最高并发度16、无法扩容Segment、性能瓶颈明显。

1.3 JDK1.8 底层原理 🔴

废弃分段锁,数据结构升级为:数组 + 链表 + 红黑树

加锁机制:CAS + synchronized 锁首节点

不再锁分段,直接锁当前哈希桶的链表头/树根节点,锁粒度极致细化。

扩容与树化阈值

  • 扩容阈值:元素数量达到容量 0.75 倍触发扩容,容量翻倍

  • 链表转红黑树:链表长度 > 8 数组长度 ≥ 64

  • 红黑树退链表:树节点数量 ≤ 6

  • 链表长度8但数组小于64:只扩容,不树化

并发扩容机制

支持多线程协助扩容。一个线程扩容,其他写入线程会帮忙迁移数据,极大提升扩容效率。

为什么不用ReentrantLock?

synchronized 在JDK1.6后经过锁升级优化,性能不输ReentrantLock;且原生关键字更轻量化、内存开销更小、JVM可深度优化。

1.4 关键坑点 🔴

  • 不保证全局一致性:get/size 是弱一致性,并发下无法精准统计总数

  • key/value 不能为null(HashMap可以),防止二义性

  • 只能保证单个put原子性,复合操作(先查后改)依旧线程不安全

二、ConcurrentSkipListMap

2.1 定位

并发有序Map,底层是跳表。对应非并发的 TreeMap。

2.2 特点

  • key 自动排序

  • 基于CAS无锁实现,并发性能高于ConcurrentHashMap

  • 适合有序、高并发场景

缺点:内存占用高,跳表每层都需要索引节点。

场景:排行榜、有序并发KV存储、时间线数据。

三、CopyOnWriteArrayList

3.1 核心原理

写时复制。所有修改操作(add/set/remove)不会直接改原数组:

  1. 加锁(ReentrantLock)

  2. 复制原数组,生成一个长度+1/-1的新数组

  3. 修改新数组

  4. 引用指针指向新数组

读操作完全无锁,直接读原数组。

3.2 优缺点

优点:读写分离、读性能爆炸高、并发极高

缺点

  • 写操作需要复制数组,内存开销大、写性能极差

  • 弱一致性:读的是旧数组快照,无法读到实时最新数据

  • 数组量大时,每次复制极度耗内存,容易GC频繁

3.3 使用场景

读多写少:白名单、配置列表、字典数据、本地缓存

绝对禁止:频繁增删改业务

四、CopyOnWriteArraySet

底层直接基于 CopyOnWriteArrayList 实现。

去重原理:新增元素时遍历数组对比,存在则不新增。

特性完全和CopyOnWriteArrayList一致:读无锁、写复制、弱一致性、适合读多写少。

五、ConcurrentLinkedQueue(无锁并发队列)

5.1 定位

JUC唯一无锁、高并发、非阻塞队列。底层基于 CAS + 链表

5.2 特点

  • 全程无锁,自旋CAS保证线程安全

  • 性能高于所有阻塞队列

  • 线程不会阻塞,失败自旋重试

缺点:大量并发会导致CAS自旋空转,消耗CPU

场景:高并发消息生产、秒杀异步队列、高性能消息缓冲

六、BlockingQueue 阻塞队列(线程池核心)

所有阻塞队列统一特性:入队、出队可阻塞、支持超时、线程同步通信,基于AQS实现。

6.1 ArrayBlockingQueue

  • 底层:定长数组

  • 有界队列,必须初始化容量

  • 全局一把ReentrantLock,读写互斥

  • 阻塞、公平/非公平可配置

场景:任务量可控、防止无限堆积OOM,线程池常用队列

6.2 LinkedBlockingQueue

  • 底层:单向链表

  • 默认无界,可指定有界

  • 两把锁:读锁、写锁分离,读写互不阻塞,并发高于Array

坑点:不指定容量就是无界队列,任务无限堆积直接OOM

6.3 SynchronousQueue

零容量队列,不存储任何元素

生产一个任务,必须等待消费者消费完成,生产者才返回

场景:newCachedThreadPool 线程池专用,任务不排队、直接新建线程

6.4 PriorityBlockingQueue

优先级阻塞队列,底层最小堆,无界,元素自带排序

场景:优先级任务调度、超时任务优先执行

6.5 DelayQueue

延时阻塞队列,元素必须实现延时接口

元素到期才能被取出,底层基于PriorityBlockingQueue

场景:订单超时关闭、延时重试、定时任务

6.6 LinkedTransferQueue

性能最强阻塞队列,融合SynchronousQueue+LinkedQueue特性,支持转移机制

七、JUC容器核心对比 🔴

7.1 锁机制区别

  • ConcurrentHashMap:CAS + synchronized 细粒度锁

  • CopyOnWrite系列:ReentrantLock 写锁,读无锁

  • BlockingQueue:AQS + ReentrantLock 可阻塞

  • ConcurrentLinkedQueue:纯CAS无锁、不阻塞

7.2 强一致性 / 弱一致性

  • 阻塞队列、ConcurrentHashMap写操作:强一致

  • CopyOnWrite、ConcurrentHashMap读操作:弱一致(可能读到旧数据)

7.3 场景选型 🔴

  • 键值存储、高并发读写 → ConcurrentHashMap

  • 键值存储、需要排序 → ConcurrentSkipListMap

  • 列表、读多写少 → CopyOnWriteArrayList

  • 高并发消息队列、不允许阻塞 → ConcurrentLinkedQueue

  • 线程池任务排队、需要阻塞控量 → Array/LinkedBlockingQueue

  • 延时任务 → DelayQueue

  • 一对一任务传递 → SynchronousQueue