《Java 并发编程·JUC 源码与容器》附录 A JUC 类图与选型表

把本卷涉及的 62 个 java.util.concurrent 类型按锁与同步器、并发容器、阻塞队列、原子类、执行器、异步编排六类摊开,逐类给一句话定位与对应章节;用 ASCII 分类树和有序性、读写比例、迭代语义、容量语义、内存放大、能否阻塞六个维度的选型表,最后落到借阅系统的具体选型。

这份附录解决什么问题

正文 45 节回答的是「这个类为什么这样实现」;选型时问题反过来:手上这个场景,该用哪个类,代价是什么。附录 A 把本卷出现的类型按功能摊平,每个类一行,给一句话定位和对应章节,供写代码时查阅。

它不重复 目录与学习路径 里的三条读法,只做两件事:查类在哪、比较取舍。分类树用来建立全局印象,六张分类表用来定位,最后的选型表用来做决定。

核实口径:65 个类型逐个 javap

本卷出现的 java.util.concurrent 类型共 62 个,外加 java.lang.invoke.VarHandle、java.lang.ThreadLocal、java.lang.InheritableThreadLocal 3 个 java.base 类型,共 65 个。写这篇附录前,在本机 Temurin 25.0.5+8 上逐个跑过 javap -p,按六类加附属类统计:

export JAVA_HOME=~/.jdks/jdk-25.0.5+8/Contents/Home
export PATH=$JAVA_HOME/bin:$PATH
# 类名从下面各表抄进 classes.txt,一行一个
while read -r c; do
  javap -p "$c" >/dev/null 2>&1 || echo "MISSING $c"
done < classes.txt
locks        total=14 ok=14 missing=0
containers   total= 8 ok= 8 missing=0
queues       total= 8 ok= 8 missing=0
atomics      total=13 ok=13 missing=0
executors    total=16 ok=16 missing=0
async        total= 3 ok= 3 missing=0
adjunct      total= 3 ok= 3 missing=0

循环没有输出任何 MISSING 行,合计 14+8+8+13+16+3+3 = 65。这说明表里的类名在本机全部存在。存在性确认不等于语义确认:字段名、方法签名、常量值仍以各节的 javap -p 与源码摘录为准。

分类树

下面这棵树按包与职责画,括号里是代表性实现。text 围栏里的箭头表示「接口到实现」或「父类到子类」,不表示依赖顺序。

java.util.concurrent
├── locks
│   ├── 接口            Lock / Condition / ReadWriteLock
│   ├── 同步器骨架      AbstractQueuedSynchronizer
│   │                   AbstractQueuedLongSynchronizer(long state)
│   ├── 阻塞原语        LockSupport(park / unpark)
│   ├── 独占锁          ReentrantLock
│   ├── 读写锁          ReentrantReadWriteLock / StampedLock
│   └── 协作器          Semaphore / CountDownLatch / CyclicBarrier
│                       Phaser / Exchanger
├── 并发容器
│   ├── 映射            ConcurrentMap → ConcurrentHashMap
│   │                   ConcurrentSkipListMap(按 key 有序)
│   ├── 集合            ConcurrentSkipListSet / CopyOnWriteArraySet
│   ├── 列表            CopyOnWriteArrayList
│   └── 无锁队列        ConcurrentLinkedQueue / ConcurrentLinkedDeque
├── 阻塞队列
│   ├── 接口            BlockingQueue
│   ├── 数组有界        ArrayBlockingQueue
│   ├── 链表            LinkedBlockingQueue / LinkedTransferQueue
│   ├── 零容量          SynchronousQueue
│   ├── 优先级          PriorityBlockingQueue
│   └── 延迟            DelayQueue → Delayed
├── atomic
│   ├── 标量            AtomicInteger / AtomicLong / AtomicReference
│   ├── 带版本          AtomicStampedReference / AtomicMarkableReference
│   ├── 数组            AtomicIntegerArray / AtomicLongArray
│   ├── 字段更新器      AtomicIntegerFieldUpdater
│   │                   AtomicReferenceFieldUpdater
│   └── 分片累加        Striped64 → LongAdder / LongAccumulator / DoubleAdder
├── 执行器
│   ├── 接口            Executor / ExecutorService
│   │                   ScheduledExecutorService / CompletionStage
│   ├── 工厂            Executors / ThreadFactory
│   ├── 线程池          ThreadPoolExecutor
│   │                   ScheduledThreadPoolExecutor
│   ├── 任务            Future / FutureTask / ForkJoinTask
│   │                   RecursiveTask / RecursiveAction
│   ├── 工作窃取        ForkJoinPool / ForkJoinWorkerThread
│   └── 异步编排        CompletableFuture
└── 结构化并发          StructuredTaskScope(JDK 25 preview)

树外还有三个 java.base 类型:VarHandle 是原子访问与内存序的底座,ThreadLocal / InheritableThreadLocal 是线程封闭的载体。它们不属于 java.util.concurrent 包:VarHandle 在第 9 章展开,两个 ThreadLocal 在卷一讨论,这里一并核实。

锁与同步器

这一组回答「谁先拿到、拿不到怎么等、等的时候能不能被打断」。

类一句话定位本卷章节
Lock显式锁接口,把 lock / unlock 从语法块变成方法2.1
Condition条件队列接口,await / signal 的契约2.2
ReadWriteLock读写锁接口,读共享、写独占3.1
AbstractQueuedSynchronizerint state + CLH 变体队列,所有 AQS 同步器的骨架1.1
AbstractQueuedLongSynchronizer同一套队列模型,state 换成 long3.1
LockSupportpark / unpark 原语,AQS 阻塞与唤醒的底座1.2
ReentrantLock可重入独占锁,公平/非公平只差 initialTryLock 与 tryAcquire2.1
ReentrantReadWriteLock读写分离,写锁可降级为读锁,读锁不能升级3.2
StampedLock版本戳 + 乐观读,不可重入,适合读多写少3.3
Semaphore许可模型,共享模式最直接的应用4.1
CountDownLatch一次性倒计数,数到 0 后所有等待者放行4.2
CyclicBarrier可重用栅栏,内部是 ReentrantLock + Condition,不走 AQS4.2
Phaser分阶段同步,可动态注册与注销参与者4.2
Exchanger两个线程在配对点交换对象,可带超时4.3

ReentrantLock 与 ReentrantReadWriteLock 的 Sync 都继承 AQS;Semaphore 与 CountDownLatch 用共享模式;CyclicBarrier 是这一组里唯一不用 AQS 的,它直接用 ReentrantLock 和 Condition 实现代际(generation)切换。

并发容器

这一组回答「多个线程读写同一个结构时,读要不要加锁、写要锁多大范围」。

类一句话定位本卷章节
ConcurrentMap并发映射接口,定义 putIfAbsent / compute 等原子复合操作5.2
ConcurrentHashMap桶数组 + 首节点 CAS + 扩容协助,读完全不加锁5.1
ConcurrentLinkedQueue无锁 FIFO 队列,MAX_HOPS = 8 控制帮助删除的步数10.1
ConcurrentLinkedDeque无锁双端队列,头尾都能进出10.1
ConcurrentSkipListMap跳表实现的有序并发映射,无 HeadIndex,head 是 Index6.2
ConcurrentSkipListSet基于 ConcurrentSkipListMap 的有序集合6.2
CopyOnWriteArrayList写时复制列表,写锁是内置 Object lock,迭代读旧数组6.1
CopyOnWriteArraySet基于 CopyOnWriteArrayList 的集合,去重靠线性扫描6.1

ConcurrentHashMap 与 CopyOnWriteArrayList 的取舍在 6.3 的矩阵里展开:前者适合「读多写少但写也要快」,后者适合「读极多、写极少、迭代要多」。两者的迭代器都不是强一致快照,只是弱一致或旧数组快照。

阻塞队列

这一组回答「生产者和消费者速度不匹配时,在哪里排队、排不下怎么办」。

类一句话定位本卷章节
BlockingQueue阻塞队列接口,put / take 可阻塞,offer / poll 可超时7.1
ArrayBlockingQueue数组 + 单锁 + 两个条件,容量必填7.1
LinkedBlockingQueue链表 + 双锁(putLock / takeLock),容量默认近似无界7.1
SynchronousQueue零容量,生产者必须等到消费者接手7.2
PriorityBlockingQueue无界优先级堆,元素必须可比较7.2
DelayQueue无界延迟队列,只有到期元素才能被取走7.2
Delayed延迟元素接口,getDelay 决定还有多久到期7.2
LinkedTransferQueue链表无锁传输队列,transfer 会等到消费者接手7.2

JDK 25 的 SynchronousQueue 内部是 Transferer<E> extends LinkedTransferQueue<E>,公平模式另有 qlock、waitingProducers、waitingConsumers 三个字段。旧书里的 TransferStack / TransferQueue 在本机 javap 找不到,附录末尾有记录。

原子类

这一组回答「不加锁怎么保证单变量更新的原子性,竞争高时怎么减少 CAS 失败」。

类一句话定位本卷章节
AtomicIntegerint 的 CAS 封装,JDK 25 仍走 jdk.internal.misc.Unsafe8.1
AtomicLonglong 的 CAS 封装,低竞争计数的默认选择8.1
AtomicReference对象引用的 CAS 封装8.1
AtomicStampedReference引用 + int 版本戳,用来识别 ABA10.2
AtomicMarkableReference引用 + boolean 标记,只关心「有没有动过」10.2
AtomicIntegerArray数组元素的原子更新,索引参与寻址9.3
AtomicLongArraylong 数组元素的原子更新9.3
AtomicIntegerFieldUpdater用反射拿到字段偏移,原子更新已有类的 volatile int8.1
AtomicReferenceFieldUpdater同上,目标字段是对象引用8.1
Striped64base + Cell[] 的分片累加骨架,包内可见8.3
LongAdder高并发计数,sum() 是弱一致估算8.2
LongAccumulator可自定义累加函数的分片累加器8.2
DoubleAdderdouble 版分片累加,语义与 LongAdder 相同8.2

注意 AtomicInteger 的实现路径:javap -p -c 能看到 getAndAdd 调用 Unsafe.getAndAddInt,不是 VarHandle。第 9 章讲的是「新代码怎么用 VarHandle」,不是「JDK 内部已经全换了」。

执行器与任务

这一组回答「任务交给谁跑、线程怎么复用、异常怎么回到调用方」。

类一句话定位本卷章节
Executor只声明 execute(Runnable) 的最小型执行接口11.1
ExecutorService增加 submit / shutdown / awaitTermination11.2
ScheduledExecutorService增加延迟与周期任务11.3
AbstractExecutorService用 FutureTask 把 submit 搭在 execute 上的骨架11.2
Executors线程池与队列的工厂方法,方便但容易掩盖容量问题11.1
ThreadFactory线程创建策略,负责命名与守护属性11.1
Future异步结果的读取、取消、超时接口13.3
FutureTaskFuture + Runnable 的最小实现,状态机只有七种11.2
ThreadPoolExecutorctl 高 3 位状态、低 29 位线程数,Worker 继承 AQS11.1
ScheduledThreadPoolExecutor用 DelayedWorkQueue 堆 + leader 实现定时11.3
ExecutorCompletionService按完成顺序取 Future,而不是按提交顺序13.1
ForkJoinPool工作窃取池,ctl 是 long,WorkQueue 用 seqlock12.1
ForkJoinTask可 fork / join 的任务基类,轻量于普通线程12.1
RecursiveTask有返回值的递归分治任务12.3
RecursiveAction无返回值的递归分治任务12.3
ForkJoinWorkerThread窃取池里的工作线程,绑定一个 WorkQueue12.2

异步编排与结构化并发

这一组只有三个类型,但第 13 章整章围绕第一个展开。

类一句话定位本卷章节
CompletableFuture依赖栈 + 完成回调,result 与 stack 两个 volatile 字段13.1–13.3
CompletionStage异步阶段的组合契约,定义 thenApply / thenCompose 等13.1
StructuredTaskScopeJDK 25 preview 的结构化并发作用域,open / fork / join13.2 (使用需 --enable-preview)

StructuredTaskScope 在 JDK 25 仍是 preview,且 ShutdownOnFailure / ShutdownOnSuccess 已不存在;本机 javap 对这两个嵌套类报「找不到类」。写作与选型时只能把它当作「未来可能替代部分 CompletableFuture 扇出」的方向,不能当稳定 API 用。

选型表:六个维度

六个维度的含义先说清楚,否则表格只能背不能推:

  • 有序性:迭代或取出的顺序是否可预测。
  • 读写比例:结构更适合读多写少、读写均衡,还是写多。
  • 迭代语义:迭代器看到的是弱一致视图、快照,还是根本不支持迭代。
  • 容量语义:无界、有界、零容量、按优先级、按到期时间。
  • 内存放大:每个元素相对裸值的额外对象与指针开销。
  • 能否阻塞:生产/消费或加锁时,调用线程是否会被挂起。
类型有序性读写比例迭代语义容量语义内存放大能否阻塞
ConcurrentHashMap无序读多写少弱一致无界(可给初始容量)中,每桶一个 Node否
ConcurrentSkipListMap按 key 排序读多写少弱一致无界高,多层索引否
CopyOnWriteArrayList插入序读极多写极少旧数组快照无界高,每次写复制整个数组否
ArrayBlockingQueueFIFO读写均衡弱一致有界,必填低,纯数组是
LinkedBlockingQueueFIFO读写均衡弱一致可有界,默认近似无界高,每元素一个 Node是
SynchronousQueue无存储交接型不支持迭代零容量低是
PriorityBlockingQueue按优先级读多写少弱一致无界中,数组堆是(空时)
DelayQueue按到期时间读多写少弱一致无界中,堆 + 延迟对象是(无到期元素时)
ReentrantLock不适用不适用不适用不适用低是(lock)
StampedLock不适用读多写少不适用不适用低是(悲观读写锁);乐观读不阻塞
AtomicLong不适用低竞争写单值强一致不适用低否
LongAdder不适用高竞争写sum 弱一致不适用中,Cell[] + 缓存行填充否

读这张表的方法:先看「有序性」和「容量语义」能不能满足业务约束,再看「读写比例」和「内存放大」决定值不值得。比如同样是有界 FIFO,ArrayBlockingQueue 与 LinkedBlockingQueue 的差别不在功能,而在锁的粒度和每元素的内存开销;同样是有序映射,ConcurrentSkipListMap 的排序能力要用内存和常数因子换。

如果不想逐列比,可以按下面四个问题走一遍,大多数场景三步内就能收敛:

这个结构需要在两个线程之间传递任务吗?
├── 不需要:只是共享读写
│   ├── 需要按 key 或到期时间排序?
│   │   ├── 是 → ConcurrentSkipListMap
│   │   └── 否 → ConcurrentHashMap
│   └── 写极少、遍历极多?
│       └── 是 → CopyOnWriteArrayList
└── 需要:生产者 / 消费者
    ├── 必须限制在途数量? → ArrayBlockingQueue
    ├── 必须手把手交接、不存储? → SynchronousQueue
    ├── 按到期时间取? → DelayQueue
    ├── 按优先级取? → PriorityBlockingQueue
    └── 只是普通缓冲? → LinkedBlockingQueue(显式给容量)

四个问题分别对应「有没有跨线程传递」「要不要顺序」「写有多频繁」「容量怎么定义」。答完还定不下来,通常是需求本身没写清,而不是类选错。

落到借阅系统

把表压到 libraryd-conc 的具体位置,选型就不抽象了:

场景选择理由章节
按 ISBN 查图书ConcurrentHashMap读多写少,单次 get 无锁5.2
预约按到期时间排序ConcurrentSkipListMap需要按 dueAt 范围查询6.2
通知订阅名单CopyOnWriteArrayList几乎不变,遍历远多于写6.1
借书请求缓冲ArrayBlockingQueue必须限流,满了让生产者阻塞7.1
还书事件日志LinkedBlockingQueue双锁,生产消费可并行7.1
预约到期提醒DelayQueue只有到期元素才能出队7.2
借阅次数统计LongAdder高并发写,允许 sum() 弱一致8.2
单本库存扣减AtomicInteger 或 CHM.compute单变量 CAS;跨字段用 compute8.1
下游并发上限Semaphore许可数就是并发上限4.1
启动预热CountDownLatch一次性等待所有预热完成4.2
目录缓存乐观读StampedLock读多写少,版本戳校验3.3
异步通知扇出CompletableFuture三个下游并行,统一收口13.1

举一个容易选错的:借书请求缓冲不要用 LinkedBlockingQueue 的默认构造器。它默认容量是 Integer.MAX_VALUE,看起来「不会满」,实际是把背压消失了,高峰期请求会堆到 OOM。用 ArrayBlockingQueue(1024),满了让 put 阻塞,或者用 offer(timeout) 明确拒绝,才是 7.1 讲的容量语义。

本机 javap 找不到的类

为了不和旧版教材混淆,下面这些名字在本机 Temurin 25.0.5+8 上逐个 javap -p 过,全部报「找不到类」:

MISSING java.util.concurrent.ConcurrentSkipListMap$HeadIndex
MISSING java.util.concurrent.SynchronousQueue$TransferStack
MISSING java.util.concurrent.SynchronousQueue$TransferQueue
MISSING java.util.concurrent.StructuredTaskScope$ShutdownOnFailure
MISSING java.util.concurrent.StructuredTaskScope$ShutdownOnSuccess

对应的事实是:JDK 25 的 ConcurrentSkipListMap 用 Index<K,V> 做头,没有 HeadIndex;SynchronousQueue 的传输器继承 LinkedTransferQueue,没有独立的 TransferStack / TransferQueue;StructuredTaskScope 的关闭策略改成 Joiner,两个 ShutdownOn* 子类已移除。引用旧书时看到这五个名字,先当历史对照处理。

小结

这张类图的用法不是背,而是三步:先在分类树里定位职责,再在分类表里找到具体类与章节,最后用六维选型表比较代价。65 个类型都在本机 javap 核实过,5 个旧版名字确认不存在;选型结论落到借阅系统时,优先看容量语义与读写比例,再看内存放大。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「books」更多文章

  1. 《Java 消息中间件·事件驱动架构》附录 B 架构决策记录模板
  2. 《Java 消息中间件·事件驱动架构》附录 A 术语表
  3. 《Java 消息中间件·事件驱动架构》15.3 全书验收