Day 010 — Java 并发编程完整体系

📅 2026/7/22 2:08:57 👁️ 阅读次数 📝 编程学习
Day 010 — Java 并发编程完整体系

📅2026-07-24 |🏷️Java · 后端方向 |⏱️建议 5h |🎯并发是 Java 面试最难也最拉分的模块


📌 今日知识地图

Java 并发面试全景 │ ├── 模块一:线程基础 & synchronized │ ├── 线程生命周期(6 态)& 创建方式 │ ├── synchronized 底层:对象头(Mark Word) / Monitor / 锁升级 │ └── 偏向锁 → 轻量级锁 → 重量级锁 完整升级路径 │ ├── 模块二:volatile & CAS │ ├── volatile:可见性 / 禁止重排(内存屏障) / 不保证原子性 │ └── CAS:Unsafe / ABA 问题 / 自旋开销 │ ├── 模块三:AQS 完整拆解 │ ├── state + CLH 队列 + acquire/release 模板方法 │ ├── 独占模式:ReentrantLock(公平/非公平)+ Condition │ └── 共享模式:CountDownLatch / Semaphore / CyclicBarrier │ ├── 模块四:线程池 │ ├── 7 参数 + 4 种拒绝策略 + 完整执行流程 │ ├── IO 密集 vs CPU 密集的线程数配置公式 │ └── 常见坑:OOM / 线程池混用 / submit 吞异常 │ ├── 模块五:ThreadLocal & JUC 工具 │ ├── ThreadLocal:ThreadLocalMap / WeakReference / 内存泄漏 │ ├── CompletableFuture:异步编排 │ └── ConcurrentHashMap 1.8 源码速读 │ └── 面试题精选(12 道 + 公司标签)

模块一:线程基础 & synchronized 底层

1.1 线程生命周期(6 态)

NEW → RUNNABLE → BLOCKED ──┐ │ ↓ │ │ WAITING ────┤ │ ↓ │ │ TIMED_WAITING ─┘ │ ↓ └──→ TERMINATED NEW: new Thread() 后、start() 前 RUNNABLE: start() 后(包含等待 CPU 调度的 Ready + 正在执行的 Running) BLOCKED: 等待获取 synchronized 锁(只能等锁释放,不能被中断) WAITING: wait() / join() / LockSupport.park() 不限时等待 TIMED_WAITING: sleep(ms) / wait(ms) / join(ms) 限时等待 TERMINATED: run() 执行完毕

1.2 synchronized 底层原理

// synchronized 三种用法:// ① 实例方法 → 锁是 this(当前实例对象)// ② 静态方法 → 锁是 Class 对象// ③ 代码块 → 锁是指定对象// 字节码层面:// 方法级:ACC_SYNCHRONIZED 标志// 代码块:monitorenter ← 进入同步块// monitorexit ← 正常退出// monitorexit ← 异常退出(编译器自动加)

对象头与锁状态

Mark Word (64 bits) 在不同锁状态下的格式: 无锁状态: unused(25) | hashcode(31) | unused(1) | 分代年龄(4) | 0(偏向锁标志) | 01(锁标志) 偏向锁: 线程ID(54) | Epoch(2) | unused(1) | 分代年龄(4) | 1(偏向锁标志) | 01(锁标志) 轻量级锁: 指向栈中锁记录的指针(62) | 00(锁标志) 重量级锁: 指向Monitor的指针(62) | 10(锁标志) GC 标记: forward_ptr(62) | 11(锁标志)

锁升级完整路径(面试必画!)

偏向锁 (Biased Locking) │ 条件:只有一个线程反复获取锁 │ 操作:CAS 将线程 ID 写入 Mark Word │ 撤销:另一个线程竞争时 → 升级 │ ▼ 轻量级锁 (Lightweight Locking) │ 条件:线程交替执行,无实际竞争 │ 操作:在栈帧创建 Lock Record → CAS 将 Mark Word 替换为 LR 指针 │ 膨胀:CAS 失败多次 / 有竞争 → 升级 │ ▼ 重量级锁 (Heavyweight Locking) │ 条件:多个线程激烈竞争 │ 操作:向 OS 申请 Mutex Lock → 未获取锁的线程进入 BLOCKED 状态 │ 唤醒:需要 OS 调度 → 用户态↔内核态切换 → 最慢
// 锁升级不可逆!只能单向升级// 但偏向锁可以被批量撤销(Bulk Revocation)// JDK 15+ 默认禁用偏向锁(-XX:+UseBiasedLocking 手动开启)// 原因:现代应用大多是线程池并发,偏向锁的撤销成本高于收益

1.3 wait/notify 机制

// ═══════════════════════════════════════// wait/notify 三要素(面试必问!)// ═══════════════════════════════════════// ① 必须在 synchronized 块中调用(因为需要先持有 Monitor)// ② wait() 会释放锁,sleep() 不释放锁// ③ notify() 随机唤醒一个等待线程,notifyAll() 唤醒所有// 经典范式:synchronized(lock){while(conditionNotMet){// ← 注意是 while 不是 if!防止虚假唤醒lock.wait();}// 执行业务逻辑lock.notifyAll();}

模块二:volatile & CAS

2.1 volatile — 轻量级同步

特性说明
可见性一个线程修改 volatile 变量后,其他线程立即可见(强制刷新到主内存)
禁止重排通过内存屏障(Memory Barrier)禁止指令重排序
不保证原子性i++这种复合操作仍然不安全
// ═══════════════════════════════════════// volatile 的经典应用:DCL 单例// ═══════════════════════════════════════publicclassSingleton{// volatile 关键!否则指令重排可能返回未初始化完毕的对象privatestaticvolatileSingletoninstance;publicstaticSingletongetInstance(){if(instance==null){// 第一次检查synchronized(Singleton.class){if(instance==null){// 第二次检查instance=newSingleton();// ← 这里可能被重排!}}}returninstance;}}// new Singleton() 的实际步骤:// ① 分配内存空间// ② 调用构造器初始化对象// ③ instance 指向内存空间// 不加 volatile → ②和③可能被重排// → 另一个线程在第一次检查时拿到未初始化完毕的对象 → 灾难// volatile 禁止重排的原理:// 在 volatile 写之前插入 StoreStore 屏障 → 禁止前面的普通写和后面的 volatile 写重排// 在 volatile 写之后插入 StoreLoad 屏障 → 禁止后面的普通读和前面的 volatile 写重排// 在 volatile 读之后插入 LoadLoad 屏障 → 禁止后面的普通读和前面的 volatile 读重排// 在 volatile 读之后插入 LoadStore 屏障 → 禁止后面的普通写和前面的 volatile 读重排

2.2 CAS(Compare And Swap)

// CAS(V, A, B):如果 V 的值等于 A,则将 V 更新为 B;否则不更新// 底层:CPU 的 cmpxchg 指令(原子操作)// Java 实现:Unsafe.compareAndSwapInt/compareAndSwapObject// CAS 的三大问题:// ① ABA 问题:V 从 A→B→A,CAS 检测不到变化// 解决:AtomicStampedReference(版本号)/ AtomicMarkableReference(布尔标记)// ② 自旋开销:CAS 失败后循环重试 → CPU 空转// ③ 只能保证一个共享变量的原子性// 解决:AtomicReference(包装多个变量)

模块三:AQS 完整拆解(面试最难模块)

3.1 AQS 是什么?

AQS (AbstractQueuedSynchronizer) = Java 并发包的基石 核心:一个 volatile int state + 一个 FIFO 的 CLH 双向链表队列 所有 JUC 锁和同步器的实现: ReentrantLock → AQS(独占模式) CountDownLatch → AQS(共享模式) Semaphore → AQS(共享模式) ReentrantReadWriteLock → AQS(读写模式) CyclicBarrier → ReentrantLock + Condition(间接用 AQS) 当你理解了 AQS,你就理解了 Java 并发的一半。

3.2 AQS 核心设计

// ═══════════════════════════════════════// AQS 核心三要素:// ═══════════════════════════════════════// ① state:同步状态(volatile int)// - ReentrantLock:state=0(未锁) / state>0(重入次数)// - Semaphore:state=N(剩余许可数)// - CountDownLatch:state=N(需要等待的线程数)// ② CLH 队列:双向链表,存放等待获取锁的线程// 节点有状态:CANCELLED(1)/SIGNAL(-1)/CONDITION(-2)/PROPAGATE(-3)// ③ 模板方法模式:// 子类重写:// tryAcquire(int) — 独占获取// tryRelease(int) — 独占释放// tryAcquireShared — 共享获取// tryReleaseShared — 共享释放// 父类提供:// acquire() / acquireShared() — 获取锁(排队+阻塞)// release() / releaseShared() — 释放锁(唤醒后继)
// ═══════════════════════════════════════// acquire() 核心流程(简化)// ═══════════════════════════════════════publicfinalvoidacquire(intarg){// ① 尝试获取锁(子类实现)→ 成功就返回// ② 失败 → addWaiter():创建节点,CAS 加入 CLH 队列尾部// ③ acquireQueued():自旋 + park// 前驱是 head → 再尝试获取一次(可能 head 刚释放)// 前驱不是 head → shouldParkAfterFailedAcquire() 检查前驱状态// 前驱 SIGNAL = true → parkAndCheckInterrupt() → LockSupport.park(this)// 前驱 CANCELLED → 跳过取消的节点// 前驱其他 → CAS 设为 SIGNALif(!tryAcquire(arg)&&acquireQueued(addWaiter(Node.EXCLUSIVE),arg))selfInterrupt();}

3.3 ReentrantLock — 独占锁

// ═══════════════════════════════════════// 公平锁 vs 非公平锁// ═══════════════════════════════════════// 公平锁(FairSync):// tryAcquire() 中先检查 CLH 队列中是否有排在前面的线程 → 有就排队// 非公平锁(NonfairSync):// tryAcquire() 直接 CAS 抢 → 抢不到再排队// 默认是非公平锁(性能更好,减少线程切换)// ═══════════════════════════════════════// 可重入原理// ═══════════════════════════════════════// state = 0 → 未锁定// state = 1 → 锁被持有(无重入)// state = N → 重入 N 次// ═══════════════════════════════════════// synchronized vs ReentrantLock(面试必问对比!)// ═══════════════════════════════════════// synchronized ReentrantLock// 实现 JVM 级别,C++实现 JDK 级别,Java 实现// 锁释放 自动(代码块结束/异常) 手动 unlock()(finally 中)// 可中断 不可中断 lockInterruptibly()// 公平性 非公平 可选公平/非公平// 条件变量 一个对象的 wait/notify 多个 Condition// 尝试获取 阻塞等待 tryLock(超时) 立即返回// 性能 JDK 6+ 优化后接近 略微更灵活// 选择 默认用 sync 需要高级功能时用 Lock

3.4 CountDownLatch / Semaphore / CyclicBarrier

// ═══════════════════════════════════════// CountDownLatch:一等多(主线程等 N 个子线程完成)// ═══════════════════════════════════════CountDownLatchlatch=newCountDownLatch(5);// state = 5// 子线程:latch.countDown(); → state--// 主线程:latch.await(); → 等 state == 0// 不可以重置!// ═══════════════════════════════════════// Semaphore:控制同时访问资源的线程数// ═══════════════════════════════════════Semaphoresem=newSemaphore(3);// 最多 3 个线程同时访问sem.acquire();// state--,state=0 时阻塞sem.release();// state++// ═══════════════════════════════════════// CyclicBarrier:多等多(N 个线程互相等待到齐)// ═══════════════════════════════════════CyclicBarrierbarrier=newCyclicBarrier(5,()->{// 所有线程到齐后执行的"汇总任务"System.out.println("全部到齐,开始下一阶段!");});// 每个线程:barrier.await(); → 等到最后一个人来才继续// 可重用!(Cyclic)

模块四:线程池

4.1 7 参数 + 执行流程

ThreadPoolExecutor(intcorePoolSize,// 核心线程数intmaximumPoolSize,// 最大线程数longkeepAliveTime,// 非核心线程的空闲存活时间TimeUnitunit,BlockingQueue<Runnable>workQueue,// 任务队列ThreadFactorythreadFactory,RejectedExecutionHandlerhandler// 拒绝策略);// ═══════════════════════════════════════// 完整执行流程(面试必须背下来!)// ═══════════════════════════════════════// 提交任务 →// ① 核心线程数未满 → 创建核心线程执行// ② 核心线程已满 → 放入工作队列等待// ③ 队列已满 → 创建非核心线程(直到 maximumPoolSize)// ④ 线程数已达 max + 队列已满 → 执行拒绝策略

4.2 四种拒绝策略

策略行为适用场景
AbortPolicy(默认)抛 RejectedExecutionException任务不能丢
CallerRunsPolicy交给调用线程执行既不能丢也不能让调用方等(背压)
DiscardPolicy直接丢弃(静默)不重要任务
DiscardOldestPolicy丢弃队列中最旧的任务宁愿丢旧的也要执行新的

4.3 线程数配置公式

// CPU 密集型(计算、加密、压缩):// 线程数 = CPU 核数 + 1// 理由:额外的 1 个线程在 CPU 空闲时补位// IO 密集型(数据库查询、网络请求、文件读写):// 线程数 = CPU 核数 × 2// 或:CPU 核数 × (1 + 平均等待时间 / 平均计算时间)// 更精确:CPU 核数 / (1 - 阻塞系数),阻塞系数 ≈ 0.8~0.9// IO 密集的阻塞系数通常很高 → 线程数远大于 CPU 核数是合理的

4.4 线程池的五个坑

// ═══════════════════════════════════════// 坑 1:Executors.newFixedThreadPool 无界队列 → OOM// ═══════════════════════════════════════// LinkedBlockingQueue 的 capacity 默认是 Integer.MAX_VALUE// → 任务堆积 → 内存耗尽// 修复:用 ArrayBlockingQueue 或设置 LinkedBlockingQueue 的 capacity// ═══════════════════════════════════════// 坑 2:submit() 吞异常// ═══════════════════════════════════════executor.submit(()->{thrownewRuntimeException("失败");});// → 没有日志输出!异常被封装在 Future 中// 修复:future.get() 获取异常 / execute() 代替 submit() / 重写 afterExecute()// ═══════════════════════════════════════// 坑 3:线程池混用(IO 和 CPU 任务用同一个池)// ═══════════════════════════════════════// → IO 任务占满线程 → CPU 任务永远得不到执行// 修复:不同类型的任务用不同的线程池// ═══════════════════════════════════════// 坑 4:ThreadLocal + 线程池 = 内存泄漏 + 数据串用// ═══════════════════════════════════════// 线程复用时 ThreadLocal 没清理 → 上次任务的数据污染下次任务// 修复:finally { threadLocal.remove(); }// ═══════════════════════════════════════// 坑 5:shutdown() vs shutdownNow()// ═══════════════════════════════════════// shutdown():等已提交的任务执行完再关闭(优雅)// shutdownNow():立即中断所有线程并返回未执行的任务列表

模块五:ThreadLocal & JUC 工具

5.1 ThreadLocal:原理 + 内存泄漏

// ═══════════════════════════════════════// 数据结构// ═══════════════════════════════════════// 每个 Thread 有一个 ThreadLocalMap// ThreadLocalMap 的 Entry 继承 WeakReference<ThreadLocal<?>>// → key 是弱引用(ThreadLocal 对象),value 是强引用(存入的值)// → 当 ThreadLocal 对象没有外部强引用时,GC 回收 key// → 但 value 仍然是强引用 → 内存泄漏!// ═══════════════════════════════════════// 内存泄漏解决方案// ═══════════════════════════════════════// ① 主动调用 ThreadLocal.remove()(最佳实践!)// ② ThreadLocalMap 在 get/set 时会顺便清理 key==null 的 Entry// ③ 但长时间不调用 get/set 的线程仍有泄漏风险// 为什么用弱引用作为 key?// → 如果 key 是强引用,只要线程还活着,ThreadLocal 就不能被 GC// → 但实践中 ThreadLocal 通常用 static 修饰,本身就不会被 GC// → 所以很多文章说"弱引用解决了内存泄漏"是不准确的// ═══════════════════════════════════════// 正确使用范式// ═══════════════════════════════════════publicclassRequestContext{privatestaticfinalThreadLocal<Map<String,Object>>CONTEXT=newThreadLocal<>();publicstaticvoidset(Stringkey,Objectval){Map<String,Object>map=CONTEXT.get();if(map==null){map=newHashMap<>();CONTEXT.set(map);}map.put(key,val);}publicstaticObjectget(Stringkey){Map<String,Object>map=CONTEXT.get();returnmap!=null?map.get(key):null;}publicstaticvoidclear(){CONTEXT.remove();// ★ 必须在 finally 中调用!}}

5.2 CompletableFuture — 异步编排

// ═══════════════════════════════════════// 常见用法// ═══════════════════════════════════════// ① 异步执行CompletableFuture<String>future=CompletableFuture.supplyAsync(()->{returnheavyComputation();});// ② 链式处理future.thenApply(result->result.toUpperCase())// 转换.thenAccept(result->System.out.println(result))// 消费.thenRun(()->System.out.println("完成"));// 不关心结果// ③ 组合多个 FutureCompletableFuture.allOf(f1,f2,f3).join();// 全部完成CompletableFuture.anyOf(f1,f2,f3).join();// 任意一个完成// ④ 异常处理future.exceptionally(ex->"默认值")// 异常时返回默认值.handle((res,ex)->ex==null?res:"降级");

面试题精选(12 道)

Q1. synchronized 底层原理?锁升级过程?(字节/阿里/美团 最高频)

标准回答

synchronized 基于对象的 Monitor 实现。JDK 6 之后引入了锁升级:偏向锁(记录线程 ID,无竞争高效)→ 轻量级锁(CAS 自旋,适用于线程交替执行)→ 重量级锁(OS Mutex,线程 BLOCKED)。

偏向锁在第一个线程获取时 CAS 写入线程 ID,第二个线程竞争时撤销偏向并膨胀为轻量级锁。轻量级锁自旋获取失败次数过多(默认 10 次或等待线程超过 CPU 核数一半)→ 膨胀为重量级锁。JDK 15+ 默认禁用偏向锁。

Q2. synchronized 和 ReentrantLock 区别?(阿里/拼多多 高频)

标准回答

synchronized 是 JVM 关键字,自动释放锁,不可中断,非公平,单一条件。ReentrantLock 是 JDK 类,需手动 unlock()(finally 中),支持可中断获取(lockInterruptibly)、tryLock 超时获取、公平/非公平可选、多个 Condition。

选择原则:优先 synchronized(简洁安全),需要可中断/超时/公平锁/多条件时用 ReentrantLock。

Q3. volatile 的作用?为什么不能保证原子性?(字节/美团)

标准回答

volatile 保证可见性(修改后立即刷新到主内存)和禁止指令重排(内存屏障),但不保证原子性。i++是三步操作(读→改→写),volatile 不能阻止两个线程同时读到同一个值再去改。需要用 synchronized 或 AtomicInteger 保证原子性。

经典应用:DCL 单例中的 instance 必须用 volatile,防止指令重排导致返回未初始化完毕的对象。

Q4. AQS 原理?(阿里/百度 高频)

标准回答

AQS 是 JUC 的基石,核心是 volatile int state(同步状态)+ FIFO 的 CLH 双向链表队列(等待线程)。子类重写 tryAcquire/tryRelease,父类提供 acquire/release(排队+阻塞+唤醒)。

ReentrantLock 用 state=0/1/N 表示锁状态(0=未锁定,N=重入次数),CountDownLatch 用 state=N 倒计数(await 等 state=0),Semaphore 用 state 表示剩余许可。

Q5. 线程池执行流程?(字节/阿里/美团 必考)

标准回答

提交任务 → ① 核心线程未满 → 创建核心线程;② 核心满 → 入队列;③ 队列满 → 创建非核心线程(到 max);④ 达到 max + 队列满 → 拒绝策略。核心线程默认不回收(allowCoreThreadTimeOut=true 则超时也回收)。

Q6. 线程池的核心线程数怎么设置?(腾讯/字节)

标准回答

CPU 密集 = CPU 核数 + 1(多一个在 CPU 空闲时补位)。IO 密集 = CPU 核数 × 2 或 CPU 核数 × (1 + WT/ST)(等待时间/计算时间)。更精确公式:CPU 核数 / (1 - 阻塞系数)。最终通过压测验证和调整。

Q7. ThreadLocal 内存泄漏的原因和解决方案?(字节/阿里 高频)

标准回答

ThreadLocalMap 的 Entry 中 key 是弱引用(ThreadLocal 对象),value 是强引用(存储的值)。ThreadLocal 外部强引用断开后,key 被 GC,但 value 仍在 ThreadLocalMap 中且无法被访问和回收 → 内存泄漏。虽然 ThreadLocalMap 在 get/set 时会顺手清理,但不活跃线程仍有风险。

解决:① 每次使用后在 finally 中调用 ThreadLocal.remove()(最有效);② 用 static 修饰 ThreadLocal 防止被 GC(但这不解决泄漏,只是防止 null key)。

Q8. CAS 的 ABA 问题怎么解决?(美团/阿里)

标准回答

ABA 问题:共享变量从 A→B→A,CAS 看到还是 A 以为没变。用 AtomicStampedReference(版本号,每次更新 stamp+1)或 AtomicMarkableReference(布尔标记,关心是否被动过)解决。

Q9. CountDownLatch 和 CyclicBarrier 的区别?(字节)

标准回答

CountDownLatch 是一等多(一个线程等 N 个),不可重置。CyclicBarrier 是多等多(N 个线程互相等),可循环使用。CountDownLatch 基于 AQS 共享模式,CyclicBarrier 基于 ReentrantLock + Condition。

Q10. CompletableFuture 和 Future 区别?(阿里)

标准回答

Future 是阻塞获取结果(get() 阻塞),不能链式调用,不能组合多个异步任务。CompletableFuture 支持 thenApply/thenCompose/thenCombine 等链式回调,支持 allOf/anyOf 组合多个任务,支持 exception 处理,实现真正的异步编程。底层用 ForkJoinPool。

Q11. 为什么不建议用 Executors 创建线程池?(阿里规范 高频)

标准回答

newFixedThreadPool 和 newSingleThreadExecutor 使用无界 LinkedBlockingQueue → 任务堆积 → OOM。newCachedThreadPool 允许无限创建线程(max=Integer.MAX_VALUE) → 线程爆炸 → OOM。newScheduledThreadPool 最大线程数也是 Integer.MAX_VALUE。直接用 ThreadPoolExecutor 指定所有参数,强制知晓线程池的运行规则。

Q12. ConcurrentHashMap 1.8 怎么保证线程安全?(字节/阿里)

标准回答

放弃 1.7 的分段锁,改用 Node[] + CAS + synchronized。桶为空时 CAS 插入,桶非空时 synchronized(头节点)。锁粒度从 Segment 降到桶级别。多线程协助扩容(transfer 中每个线程领取一段桶区间独立迁移)。size 计算用 baseCount + CounterCell 数组分散竞争(类似 LongAdder)。


📊 今日知识图谱

Java 并发 DAY 10 │ ├── synchronized │ ├── 对象头(Mark Word) + Monitor + monitorenter/exit │ └── 锁升级:偏向锁→轻量级锁(CAS自旋)→重量级锁(OS Mutex) │ ├── volatile & CAS │ ├── volatile:可见性+禁止重排(内存屏障),不保证原子性 │ ├── DCL单例:volatile 防止指令重排 │ └── CAS:Unsafe.compareAndSwap,ABA→版本号解决 │ ├── AQS(JUC基石) │ ├── volatile state + CLH双向队列 │ ├── 独占:ReentrantLock(可重入/公平非公平/Condition) │ ├── 共享:CountDownLatch(state减到0) / Semaphore(许可) │ └── acquire:tryAcquire→addWaiter→acquireQueued(park) │ ├── 线程池 │ ├── 7参数 + 4拒绝策略 + 执行流程(核→队→非核→拒) │ ├── 配置:CPU密集(N+1) / IO密集(2N) │ └── 5大坑:无界队列OOM/submit吞异常/混用/TL没清理/shutdown │ └── JUC工具 ├── ThreadLocal:Map+WeakReference → 必须remove() ├── CompletableFuture:thenApply/thenCombine/allOf └── ConcurrentHashMap 1.8:CAS+sync桶级锁+多线程扩容

🔜 明日预告

Day 11 — Spring 全家桶深度拆解

  • Spring IOC:BeanFactory/ApplicationContext + Bean 生命周期 + 三级缓存循环依赖
  • Spring AOP:JDK 动态代理 vs CGLIB + @Transactional 失效 6 大场景
  • Spring Boot:自动配置原理 + starter 机制 + @Conditional
  • Spring MVC:DispatcherServlet 完整流程
  • MyBatis:核心流程 + 一二级缓存 + $ vs #
  • 8 道 Spring 高频真题

💡速通心法:并发编程三条主线——① 锁的演进(synchronized 锁升级 → ReentrantLock → AQS 体系);② 线程管理(生命周期 → 线程池 7 参数 → 配置公式);③ 并发工具(volatile/CAS → AQS → ThreadLocal → CompletableFuture)。三条线分别对应"怎么互斥"“怎么管理”“怎么协作”,串起来就是完整的并发知识体系。