一张图读懂 JUC 并发包:线程池、并发容器、AQS 与同步工具的 UML 全景类图解析
【免费下载链接】source-code-hunter😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter
导读
java.util.concurrent(简称 J.U.C)是 JDK 并发编程的基石,其中 ThreadPoolExecutor、ConcurrentHashMap、ReentrantLock、Semaphore、CountDownLatch、AtomicInteger 等组件几乎出现在每一段真实的高并发业务代码里。本文以 JUC 并发包 UML 全量类图 为骨架,结合本仓库docs/JDK/concurrentCoding目录下的系列源码笔记,按功能把 JUC 拆成线程池、并发容器、AQS 与锁、同步工具、原子类、并发集合六大分区逐一讲透,帮助你建立起"看到类名 → 想到继承关系 → 回忆源码实现"的完整知识地图。
一、为什么要用类图的方式学习 JUC
面对 JUC 包中上百个类,逐行读源码容易陷入"只见树木不见森林"。用类图把接口、抽象类、实现类之间的继承与组合关系可视化后,可以快速回答三个关键问题:
- 这个类从哪里来:例如 ThreadPoolExecutor 的继承链是
ThreadPoolExecutor → AbstractExecutorService → ExecutorService → Executor,理解了这条链,就知道它为什么天然具备submit()、shutdown()能力; - 这个类依赖什么:例如 ReentrantLock、Semaphore、CountDownLatch 的底层同步逻辑全部委托给 AbstractQueuedSynchronizer(AQS),掌握 AQS 就等于掌握了半壁 JUC;
- 这个类属于哪个功能分区:JUC 的类按功能可划分为六大区域,其中线程池及其相关类、并发容器、AQS 与锁与同步工具类、原子类是重中之重。
本仓库作者的原话是:"源码看多了再去整理这个图,感觉还是很爽的……看着这些类,回想一下其中的源码实现,感觉能侃一天。"本文就把这张图拆开,逐区展开讲。
二、六大功能分区总览
根据功能,JUC 包中的类大致划分为六个部分,核心分区及其代表类如下:
| 功能分区 | 代表接口 / 类 | 职责 |
|---|---|---|
| 线程池及执行框架 | Executor、ExecutorService、ThreadPoolExecutor、ScheduledThreadPoolExecutor、Executors、FutureTask | 任务提交、线程复用、任务调度与关闭 |
| 并发容器 | ConcurrentHashMap、ConcurrentLinkedQueue、CopyOnWriteArrayList、BlockingQueue 系列队列 | 线程安全的数据存储与传递 |
| AQS 与锁 | AbstractQueuedSynchronizer、ReentrantLock、ReentrantReadWriteLock、Condition | 同步状态管理、阻塞与唤醒、互斥与读写控制 |
| 同步工具类 | Semaphore、CountDownLatch、CyclicBarrier、Future / CompletableFuture | 多线程协作、限流、等待与汇合 |
| 原子类 | AtomicInteger、AtomicLong、AtomicReference、AtomicIntegerArray、字段更新器 | 基于 CAS 的无锁原子操作 |
| 并发集合辅助 | ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArraySet 等 | 有序并发容器与写时复制容器 |
下面逐一深入。
三、分区一:线程池及执行框架(Executor 家族)
3.1 继承链与角色划分
线程池分区的类图结构如下:
从图中可以清晰看到分层设计(对应源码笔记 Executor 线程池组件):
- Executor 接口:最顶层的执行器,只声明一个
void execute(Runnable command),含义是"在将来的某个时间执行给定的任务,任务可以在新线程、池线程或调用线程中执行"; - ExecutorService 接口:在 Executor 基础上扩展了
shutdown()优雅关闭、submit()系列提交有返回值任务的方法; - AbstractExecutorService 抽象类:用模板方法模式实现了
submit()系列公共逻辑——将任务包装成RunnableFuture后调用尚未实现的execute(),把执行细节留给子类; - ThreadPoolExecutor:线程池的具体实现,内部通过
ctl一个 int 同时编码线程池运行状态与工作线程数量,维护workQueue、corePoolSize、maximumPoolSize、keepAliveTime、threadFactory、handler六大核心要素; - Executors:工具类,为开发者封装了
newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等可直接使用的线程池工厂方法。
3.2 核心接口源码
public interface Executor { /** 在将来的某个时间执行给定的 Runnable,可执行于新线程、池线程或调用线程 */ void execute(Runnable command); } public interface ExecutorService extends Executor { /** 优雅关闭:继续执行完以前提交的任务,但不再接受新任务 */ void shutdown(); /** 提交有返回值的任务,返回其未来执行完成后的结果,Future.get() 将返回任务结果 */ <T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task); }3.3 AbstractExecutorService 的模板方法
public abstract class AbstractExecutorService implements ExecutorService { /** 模板方法模式:execute() 来自 Executor 接口,本抽象类未实现,交由子类实现 */ public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); // 关键:此处调用的是尚未实现的 execute() return ftask; } public <T> Future<T> submit(Runnable task, T result) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task, result); execute(ftask); return ftask; } public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; } }3.4 ThreadPoolExecutor 的核心构造与任务执行流程
ThreadPoolExecutor 提供了多组构造方法,最终都收敛到参数最全的一个,并对参数做合法性校验:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }各参数含义与约束如下:
| 参数 | 含义 | 说明 |
|---|---|---|
corePoolSize | 核心线程数 | 不能为负数 |
maximumPoolSize | 最大线程数 | 必须大于 0 且不小于 corePoolSize |
keepAliveTime/unit | 非核心线程空闲存活时间 | 不能为负数 |
workQueue | 任务阻塞队列 | 不能为 null,常见实现见下文并发容器分区 |
threadFactory | 线程工厂 | 不能为 null,默认Executors.defaultThreadFactory() |
handler | 拒绝策略 | 不能为 null,默认AbortPolicy(抛RejectedExecutionException) |
execute(Runnable command)的任务处理流程分三步,这也是面试中反复考察的经典逻辑:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * 1、运行的线程少于 corePoolSize → 尝试开启新线程;否则尝试进入工作队列 * 2、工作队列没满 → 进入工作队列;否则判断是否超出最大线程数 * 3、未超出最大线程数 → 尝试开启新线程;否则按饱和策略处理无法执行的任务 */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (!isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }而shutdown()则是"优雅关闭":先加mainLock锁保证线程安全,将运行状态推进到 SHUTDOWN,再中断空闲工作线程,最后尝试终止线程池——它不会中断正在执行的任务,只是不再接收新任务。
3.5 Executors 工具类与实战陷阱
public class Executors { /** 固定线程数量的线程池:核心数 = 最大数 = nThreads,无界队列 */ public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } /** 单线程线程池 */ public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } /** 可缓存的弹性线程池:核心线程数 0,最大线程数 Integer.MAX_VALUE */ public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } }需要特别提醒的是:newCachedThreadPool()的最大线程数是Integer.MAX_VALUE,如果任务数在某一瞬间暴涨,这个线程池很可能把服务器撑爆。这些工厂方法底层都是 new 一个 ThreadPoolExecutor,只是帮我们预先配好了参数;理解这张类图后,遇到特殊场景应直接使用 ThreadPoolExecutor 自定义参数,而不是盲目套用 Executors。
四、分区二:AQS——所有锁与同步工具的底层框架
4.1 AQS 在类图中的枢纽地位
AQS(AbstractQueuedSynchronizer)是 Doug Lea 创作的基础框架类,JUC 中 ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 等锁与同步工具的核心实现都依赖它。因此类图中 AQS 处于枢纽位置:锁、信号量、闭锁、栅栏等全部"挂"在它下面。
AQS 主要做三件事:
- 管理同步状态(
volatile int state); - 维护同步队列(FIFO 双向链表);
- 阻塞和唤醒线程(基于
LockSupport.park/unpark)。
从行为上区分是获取锁 / 释放锁,从模式上区分是独占锁 / 共享锁(对应源码笔记 详解 AbstractQueuedSynchronizer)。
4.2 核心数据结构:内部类 Node
当共享资源被某线程占用时,其他请求线程会被阻塞进入同步队列。AQS 的同步队列通过链表实现,载体是内部类 Node:
static final class Node { /* 标记节点在共享模式下等待 */ static final Node SHARED = new Node(); /* 标记节点在独占模式下等待 */ static final Node EXCLUSIVE = null; /* 当前线程因超时或中断被取消(终结态) */ static final int CANCELLED = 1; /* 后继线程将被阻塞,当前线程释放锁或取消后需唤醒后继(由后继线程设置前驱) */ static final int SIGNAL = -1; /* 当前线程在 condition 队列中 */ static final int CONDITION = -2; /* 用于将唤醒后继线程传递下去,完善共享锁的唤醒机制 */ static final int PROPAGATE = -3; volatile int waitStatus; // 等待状态 volatile Node prev; // 前驱节点 volatile Node next; // 后继节点 volatile Thread thread; // 节点对应的线程 Node nextWaiter; // 等待队列中的后继节点 }4.3 获取 / 释放锁的核心流程
获取独占锁的整体思路是"先尝试,失败则入队阻塞,被唤醒后再试":
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }acquireQueued是核心循环:只有当前驱节点是 head 时才有资格尝试获取锁;获取成功则把自己设为 head;否则根据前驱节点的 waitStatus 决定是否阻塞自己(shouldParkAfterFailedAcquire+parkAndCheckInterrupt)。入队时采用"先设置 node.prev = t 再 CAS 更新 tail"的精妙顺序,保证任意时刻 tail.prev 不为 null、队列完整。
释放独占锁的逻辑很简洁:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; // head 状态不会是 CANCELLED,h.waitStatus != 0 等价于 h.waitStatus < 0 if (h != null && h.waitStatus != 0) unparkSuccessor(h); // 唤醒后继线程 return true; } return false; }unparkSuccessor中有一个值得琢磨的细节:当node.next为 null 或已被取消时,需要从 tail 向前遍历找到 node 之后最近的非取消节点。原因是addWaiter/enq中 CAS 成功与next赋值之间存在时间窗,读到的next == null并不代表 node 就是 tail。
共享锁与独占锁的区别在于:tryAcquireShared返回负数表示获取失败,0 表示成功但后继不会成功,正数表示成功且后继争用线程也可能成功。获取成功后调用setHeadAndPropagate设置头节点并决定是否传播唤醒,配合doReleaseShared保证在 acquire 与 release 竞争的情况下,队列中等待节点始终有办法被唤醒。
4.4 AQS 的"用户视角":模板方法
AQS 通过模板方法模式向外提供服务:子类只需实现tryAcquire/tryRelease(独占模式)或tryAcquireShared/tryReleaseShared(共享模式),线程排队、阻塞唤醒、中断处理等复杂机制全部由 AQS 骨架方法完成。这也是为什么 ReentrantLock 与 Semaphore 的源码如此"薄"——重活都在 AQS 里。
五、分区三:锁组件(Lock / ReadWriteLock)
JUC 的locks包类较少,最核心的就是Lock接口、ReentrantLock与ReentrantReadWriteLock,其类图与主要方法如下:
5.1 Lock 接口
public interface Lock { void lock(); // 获取锁 void lockInterruptibly() throws InterruptedException; // 可中断地获取锁 boolean tryLock(); // 仅当锁空闲时才获取 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 限时获取 void unlock(); // 释放锁 }5.2 ReentrantLock:公平与非公平
ReentrantLock 的所有方法都委托给内部同步器Sync(继承自 AQS),Sync有两个子类NonfairSync与FairSync,分别实现非公平与公平策略(对应源码笔记 Lock 锁组件):
- 非公平锁
NonfairSync.lock():上来先compareAndSetState(0, 1)抢一把,抢不到才走 AQS 排队,允许"插队"; - 公平锁
FairSync.tryAcquire():在compareAndSetState之前先调用hasQueuedPredecessors()判断队列中是否有排队者,有则让行; - 两种模式都支持可重入:
current == getExclusiveOwnerThread()时直接把 state 累加,释放时逐层递减到 0 才真正释放。
public ReentrantLock() { // 默认非公平 sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } public void lock() { sync.lock(); } public boolean tryLock() { return sync.nonfairTryAcquire(1); } public void unlock() { sync.release(1); }5.3 ReentrantReadWriteLock:读写分离
ReentrantReadWriteLock 在单个 AQS 的state上同时编码两种计数:高 16 位记录读锁(共享)持有数,低 16 位记录写锁(独占)持有数。通过sharedCount(c)与exclusiveCount(c)拆分使用:
tryAcquire(写锁):若读计数或写计数非 0 且持有者不是当前线程则失败;否则可重入累加;tryAcquireShared(读锁):若写锁被其他线程持有则返回 -1 失败;否则 CAS 增加SHARED_UNIT并维护每个线程的读持有计数(firstReader/cachedHoldCounter/readHolds);- 默认
new ReentrantReadWriteLock()也是非公平的,可通过fair参数指定。
读写锁适合"读多写少"场景:多个读者可并发持有读锁,写者必须独占。
六、分区四:同步工具类(Semaphore / CountDownLatch / CyclicBarrier / Future)
同步工具类用于多线程协作与流量控制,其类图位置同样挂在 AQS 之下。
6.1 Semaphore 信号量:基于 AQS 的限流器
Semaphore 可用于控制一定时间内并发执行的线程数,可应用于网关限流、资源限制(如最大可发起连接数)。由于release()释放许可时未对释放数做限制,可以通过该方法动态增加总许可数量(对应源码笔记 Semaphore)。
核心内部类Sync继承 AQS,把state直接赋值为总许可数:
abstract static class Sync extends AbstractQueuedSynchronizer { /* 赋值 state 为总许可数 */ Sync(int permits) { setState(permits); } /* 剩余许可数 */ final int getPermits() { return getState(); } /* 自旋 + CAS 非公平获取许可 */ final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } /* 自旋 + CAS 释放许可:未限制释放数,可通过 release 动态增加许可 */ protected final boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (next < current) // overflow throw new Error("Maximum permit count exceeded"); if (compareAndSetState(current, next)) return true; } } /* 自旋 + CAS 减少许可数量 */ final void reducePermits(int reductions) { ... } /* 丢弃所有许可 */ final int drainPermits() { ... } }获取许可支持公平与非公平两种模式,默认非公平:
- 公平模式:无论是否有许可,都先判断是否有线程在排队,有则进入排队,否则尝试获取许可;
- 非公平模式:无论许可是否充足,直接尝试获取许可。
从源码可见,公平与非公平的差异就在tryAcquireShared的实现:非公平版直接自旋 CAS,公平版(FairSync)先检查hasQueuedPredecessors()。
6.2 CountDownLatch / CyclicBarrier / Future 家族
- CountDownLatch:基于 AQS 共享模式,
countDown()递减 state,await()阻塞直到 state 归零,适合"等待 N 个任务完成后再继续"; - CyclicBarrier:可循环使用的栅栏,所有线程到达屏障点后一起放行,可配合
Runnable barrierAction执行汇合动作; - Future / CompletableFuture / FutureTask:异步任务结果载体。
FutureTask实现RunnableFuture(继承 Runnable + Future),线程池的submit()正是用它包装任务;CompletableFuture实现CompletionStage,支持异步编排。
七、分区五:原子类(Atomic 家族)
原子类是基于CAS(Compare-And-Swap)+ volatile实现的无锁线程安全操作,按数据类型可分为四组:
| 类型 | 代表类 |
|---|---|
| 基础类型 | AtomicInteger、AtomicLong、AtomicBoolean |
| 引用类型 | AtomicReference、AtomicMarkableReference、AtomicStampedReference |
| 数组类型 | AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray |
| 字段更新器 | AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater |
其中AtomicStampedReference/AtomicMarkableReference通过携带版本号或标记位解决 ABA 问题;字段更新器允许对已有类的volatile字段做原子更新而无需包装对象。在实战中,原子类常用于计数器、累加器等高频小对象场景,其性能优于加锁;而对并发写压力极大的计数场景,JDK8 还提供了LongAdder采用分段累加降低 CAS 竞争(本仓库 Sentinel 底层 LongAdder 的计数实现 有专题分析)。
八、分区六:并发容器(Concurrent 集合与阻塞队列)
并发容器是线程池工作队列与缓存系统的"弹药库",在类图中主要有两条主线:
8.1 并发 Map 与 List
- ConcurrentHashMap:实现
ConcurrentMap,JDK8 采用"数组 + 链表/红黑树 + CAS + synchronized 锁桶"的结构,读操作基本无锁,是本仓库 ConcurrentHashMap 专题 的核心主题; - ConcurrentSkipListMap / ConcurrentSkipListSet:基于跳表实现的有序并发容器,提供并发环境下
O(log n)的范围查询; - CopyOnWriteArrayList / CopyOnWriteArraySet:写时复制,读操作无锁,适合"读多写极少"的场景;
- ConcurrentLinkedQueue:基于 CAS 的无界非阻塞队列。
8.2 BlockingQueue 阻塞队列家族
BlockingQueue接口下有多个实现,各自适配不同线程池场景:
| 队列 | 特性 | 典型用途 |
|---|---|---|
| LinkedBlockingQueue | 链表实现,可选有界(默认 Integer.MAX_VALUE 视为无界) | Executors 的 fixed/single 线程池默认队列 |
| ArrayBlockingQueue | 数组实现,有界 | 有界任务缓冲,防止内存无限膨胀 |
| SynchronousQueue | 不存储元素,直接移交 | Executors 的 cached 线程池默认队列 |
| PriorityBlockingQueue | 支持优先级的无界阻塞队列 | 有优先级的任务调度 |
| DelayQueue | 延迟出队的无界队列 | 定时任务、缓存过期清理 |
| LinkedTransferQueue | 支持 transfer 语义的无界队列 | 生产者直接移交消费者 |
阻塞队列的take()/put()基于锁与条件队列(Condition)实现,正是 AQS 的ConditionObject的典型应用。
九、结语:从类图到源码的学习路线
JUC 类图的六大分区并非孤立存在,它们以 AQS 与阻塞队列为两大枢纽彼此咬合:线程池用阻塞队列缓冲任务,用 AQS(mainLock)保护内部状态;锁与信号量直接复用 AQS 的同步队列;原子类则提供无锁的细粒度计数。建议按以下顺序结合本仓库源码笔记逐步深入:
- 先读 JUC 并发包 UML 全量类图,在大脑中建立分区索引;
- 攻克枢纽 详解 AbstractQueuedSynchronizer,理解同步队列与 acquire/release 骨架;
- 再看 Lock 锁组件 与 Semaphore,体会 AQS 模板方法的两种应用;
- 阅读 Executor 线程池组件 与 线程池组件,掌握 execute() 三步流程与 Executors 陷阱;
- 最后回到 JUC 并发包 UML 全量类图 中的
images/JDK1.8/JUC全量UML地图.png,对照每个类回忆其源码实现,即可完成从"认识类"到"读懂源码"的闭环。
【免费下载链接】source-code-hunter😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考