资讯动态

一张图读懂 JUC 并发包:线程池、并发容器、AQS 与同步工具的 UML 全景类图解析

发布时间:2026/9/12 13:19:56 来源:尧图企业网站定制
一张图读懂 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 的底层同步逻辑全部委托给 AbstractQueuedSynchronizerAQS掌握 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 FutureT submit(CallableT task); T FutureT 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(); RunnableFutureVoid ftask newTaskFor(task, null); execute(ftask); // 关键此处调用的是尚未实现的 execute() return ftask; } public T FutureT submit(Runnable task, T result) { if (task null) throw new NullPointerException(); RunnableFutureT ftask newTaskFor(task, result); execute(ftask); return ftask; } public T FutureT submit(CallableT task) { if (task null) throw new NullPointerException(); RunnableFutureT ftask newTaskFor(task); execute(ftask); return ftask; } }3.4 ThreadPoolExecutor 的核心构造与任务执行流程ThreadPoolExecutor 提供了多组构造方法最终都收敛到参数最全的一个并对参数做合法性校验public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueRunnable 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 且不小于 corePoolSizekeepAliveTime/unit非核心线程空闲存活时间不能为负数workQueue任务阻塞队列不能为 null常见实现见下文并发容器分区threadFactory线程工厂不能为 null默认Executors.defaultThreadFactory()handler拒绝策略不能为 null默认AbortPolicy抛RejectedExecutionExceptionexecute(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 LinkedBlockingQueueRunnable()); } /** 单线程线程池 */ public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueueRunnable())); } /** 可缓存的弹性线程池核心线程数 0最大线程数 Integer.MAX_VALUE */ public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueueRunnable()); } }需要特别提醒的是newCachedThreadPool()的最大线程数是Integer.MAX_VALUE如果任务数在某一瞬间暴涨这个线程池很可能把服务器撑爆。这些工厂方法底层都是 new 一个 ThreadPoolExecutor只是帮我们预先配好了参数理解这张类图后遇到特殊场景应直接使用 ThreadPoolExecutor 自定义参数而不是盲目套用 Executors。四、分区二AQS——所有锁与同步工具的底层框架4.1 AQS 在类图中的枢纽地位AQSAbstractQueuedSynchronizer是 Doug Lea 创作的基础框架类JUC 中 ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 等锁与同步工具的核心实现都依赖它。因此类图中 AQS 处于枢纽位置锁、信号量、闭锁、栅栏等全部挂在它下面。AQS 主要做三件事管理同步状态volatile int state维护同步队列FIFO 双向链表阻塞和唤醒线程基于LockSupport.park/unpark。从行为上区分是获取锁 / 释放锁从模式上区分是独占锁 / 共享锁对应源码笔记 详解 AbstractQueuedSynchronizer。4.2 核心数据结构内部类 Node当共享资源被某线程占用时其他请求线程会被阻塞进入同步队列。AQS 的同步队列通过链表实现载体是内部类 Nodestatic 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 决定是否阻塞自己shouldParkAfterFailedAcquireparkAndCheckInterrupt。入队时采用先设置 node.prev t 再 CAS 更新 tail的精妙顺序保证任意时刻 tail.prev 不为 null、队列完整。释放独占锁的逻辑很简洁public final boolean release(int arg) { if (tryRelease(arg)) { Node h head; // head 状态不会是 CANCELLEDh.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 / ReadWriteLockJUC 的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继承自 AQSSync有两个子类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()递减 stateawait()阻塞直到 state 归零适合等待 N 个任务完成后再继续CyclicBarrier可循环使用的栅栏所有线程到达屏障点后一起放行可配合Runnable barrierAction执行汇合动作Future / CompletableFuture / FutureTask异步任务结果载体。FutureTask实现RunnableFuture继承 Runnable Future线程池的submit()正是用它包装任务CompletableFuture实现CompletionStage支持异步编排。七、分区五原子类Atomic 家族原子类是基于CASCompare-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 与 ListConcurrentHashMap实现ConcurrentMapJDK8 采用数组 链表/红黑树 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 与阻塞队列为两大枢纽彼此咬合线程池用阻塞队列缓冲任务用 AQSmainLock保护内部状态锁与信号量直接复用 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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价