2026/7/22 1:52:12

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

Day 010 — Java 并发编程完整体系 2026-07-24 |️Java · 后端方向 |⏱️建议 5h |并发是 Java 面试最难也最拉分的模块 今日知识地图Java 并发面试全景 │ ├── 模块一线程基础 synchronized │ ├── 线程生命周期6 态 创建方式 │ ├── synchronized 底层对象头(Mark Word) / Monitor / 锁升级 │ └── 偏向锁 → 轻量级锁 → 重量级锁 完整升级路径 │ ├── 模块二volatile CAS │ ├── volatile可见性 / 禁止重排内存屏障 / 不保证原子性 │ └── CASUnsafe / ABA 问题 / 自旋开销 │ ├── 模块三AQS 完整拆解 │ ├── state CLH 队列 acquire/release 模板方法 │ ├── 独占模式ReentrantLock公平/非公平 Condition │ └── 共享模式CountDownLatch / Semaphore / CyclicBarrier │ ├── 模块四线程池 │ ├── 7 参数 4 种拒绝策略 完整执行流程 │ ├── IO 密集 vs CPU 密集的线程数配置公式 │ └── 常见坑OOM / 线程池混用 / submit 吞异常 │ ├── 模块五ThreadLocal JUC 工具 │ ├── ThreadLocalThreadLocalMap / 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 CAS2.1 volatile — 轻量级同步特性说明可见性一个线程修改 volatile 变量后其他线程立即可见强制刷新到主内存禁止重排通过内存屏障Memory Barrier禁止指令重排序不保证原子性i这种复合操作仍然不安全// ═══════════════════════════════════════// volatile 的经典应用DCL 单例// ═══════════════════════════════════════publicclassSingleton{// volatile 关键否则指令重排可能返回未初始化完毕的对象privatestaticvolatileSingletoninstance;publicstaticSingletongetInstance(){if(instancenull){// 第一次检查synchronized(Singleton.class){if(instancenull){// 第二次检查instancenewSingleton();// ← 这里可能被重排}}}returninstance;}}// new Singleton() 的实际步骤// ① 分配内存空间// ② 调用构造器初始化对象// ③ instance 指向内存空间// 不加 volatile → ②和③可能被重排// → 另一个线程在第一次检查时拿到未初始化完毕的对象 → 灾难// volatile 禁止重排的原理// 在 volatile 写之前插入 StoreStore 屏障 → 禁止前面的普通写和后面的 volatile 写重排// 在 volatile 写之后插入 StoreLoad 屏障 → 禁止后面的普通读和前面的 volatile 写重排// 在 volatile 读之后插入 LoadLoad 屏障 → 禁止后面的普通读和前面的 volatile 读重排// 在 volatile 读之后插入 LoadStore 屏障 → 禁止后面的普通写和前面的 volatile 读重排2.2 CASCompare And Swap// CAS(V, A, B)如果 V 的值等于 A则将 V 更新为 B否则不更新// 底层CPU 的 cmpxchg 指令原子操作// Java 实现Unsafe.compareAndSwapInt/compareAndSwapObject// CAS 的三大问题// ① ABA 问题V 从 A→B→ACAS 检测不到变化// 解决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// - ReentrantLockstate0(未锁) / state0(重入次数)// - SemaphorestateN(剩余许可数)// - CountDownLatchstateN(需要等待的线程数)// ② 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 需要高级功能时用 Lock3.4 CountDownLatch / Semaphore / CyclicBarrier// ═══════════════════════════════════════// CountDownLatch一等多主线程等 N 个子线程完成// ═══════════════════════════════════════CountDownLatchlatchnewCountDownLatch(5);// state 5// 子线程latch.countDown(); → state--// 主线程latch.await(); → 等 state 0// 不可以重置// ═══════════════════════════════════════// Semaphore控制同时访问资源的线程数// ═══════════════════════════════════════SemaphoresemnewSemaphore(3);// 最多 3 个线程同时访问sem.acquire();// state--state0 时阻塞sem.release();// state// ═══════════════════════════════════════// CyclicBarrier多等多N 个线程互相等待到齐// ═══════════════════════════════════════CyclicBarrierbarriernewCyclicBarrier(5,()-{// 所有线程到齐后执行的汇总任务System.out.println(全部到齐开始下一阶段);});// 每个线程barrier.await(); → 等到最后一个人来才继续// 可重用Cyclic模块四线程池4.1 7 参数 执行流程ThreadPoolExecutor(intcorePoolSize,// 核心线程数intmaximumPoolSize,// 最大线程数longkeepAliveTime,// 非核心线程的空闲存活时间TimeUnitunit,BlockingQueueRunnableworkQueue,// 任务队列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 线程池的五个坑// ═══════════════════════════════════════// 坑 1Executors.newFixedThreadPool 无界队列 → OOM// ═══════════════════════════════════════// LinkedBlockingQueue 的 capacity 默认是 Integer.MAX_VALUE// → 任务堆积 → 内存耗尽// 修复用 ArrayBlockingQueue 或设置 LinkedBlockingQueue 的 capacity// ═══════════════════════════════════════// 坑 2submit() 吞异常// ═══════════════════════════════════════executor.submit(()-{thrownewRuntimeException(失败);});// → 没有日志输出异常被封装在 Future 中// 修复future.get() 获取异常 / execute() 代替 submit() / 重写 afterExecute()// ═══════════════════════════════════════// 坑 3线程池混用IO 和 CPU 任务用同一个池// ═══════════════════════════════════════// → IO 任务占满线程 → CPU 任务永远得不到执行// 修复不同类型的任务用不同的线程池// ═══════════════════════════════════════// 坑 4ThreadLocal 线程池 内存泄漏 数据串用// ═══════════════════════════════════════// 线程复用时 ThreadLocal 没清理 → 上次任务的数据污染下次任务// 修复finally { threadLocal.remove(); }// ═══════════════════════════════════════// 坑 5shutdown() vs shutdownNow()// ═══════════════════════════════════════// shutdown()等已提交的任务执行完再关闭优雅// shutdownNow()立即中断所有线程并返回未执行的任务列表模块五ThreadLocal JUC 工具5.1 ThreadLocal原理 内存泄漏// ═══════════════════════════════════════// 数据结构// ═══════════════════════════════════════// 每个 Thread 有一个 ThreadLocalMap// ThreadLocalMap 的 Entry 继承 WeakReferenceThreadLocal?// → key 是弱引用ThreadLocal 对象value 是强引用存入的值// → 当 ThreadLocal 对象没有外部强引用时GC 回收 key// → 但 value 仍然是强引用 → 内存泄漏// ═══════════════════════════════════════// 内存泄漏解决方案// ═══════════════════════════════════════// ① 主动调用 ThreadLocal.remove()最佳实践// ② ThreadLocalMap 在 get/set 时会顺便清理 keynull 的 Entry// ③ 但长时间不调用 get/set 的线程仍有泄漏风险// 为什么用弱引用作为 key// → 如果 key 是强引用只要线程还活着ThreadLocal 就不能被 GC// → 但实践中 ThreadLocal 通常用 static 修饰本身就不会被 GC// → 所以很多文章说弱引用解决了内存泄漏是不准确的// ═══════════════════════════════════════// 正确使用范式// ═══════════════════════════════════════publicclassRequestContext{privatestaticfinalThreadLocalMapString,ObjectCONTEXTnewThreadLocal();publicstaticvoidset(Stringkey,Objectval){MapString,ObjectmapCONTEXT.get();if(mapnull){mapnewHashMap();CONTEXT.set(map);}map.put(key,val);}publicstaticObjectget(Stringkey){MapString,ObjectmapCONTEXT.get();returnmap!null?map.get(key):null;}publicstaticvoidclear(){CONTEXT.remove();// ★ 必须在 finally 中调用}}5.2 CompletableFuture — 异步编排// ═══════════════════════════════════════// 常见用法// ═══════════════════════════════════════// ① 异步执行CompletableFutureStringfutureCompletableFuture.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)-exnull?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 用 state0/1/N 表示锁状态0未锁定N重入次数CountDownLatch 用 stateN 倒计数await 等 state0Semaphore 用 state 表示剩余许可。Q5. 线程池执行流程字节/阿里/美团 必考标准回答提交任务 → ① 核心线程未满 → 创建核心线程② 核心满 → 入队列③ 队列满 → 创建非核心线程到 max④ 达到 max 队列满 → 拒绝策略。核心线程默认不回收allowCoreThreadTimeOuttrue 则超时也回收。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→ACAS 看到还是 A 以为没变。用 AtomicStampedReference版本号每次更新 stamp1或 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 允许无限创建线程maxInteger.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 防止指令重排 │ └── CASUnsafe.compareAndSwapABA→版本号解决 │ ├── AQSJUC基石 │ ├── volatile state CLH双向队列 │ ├── 独占ReentrantLock(可重入/公平非公平/Condition) │ ├── 共享CountDownLatch(state减到0) / Semaphore(许可) │ └── acquiretryAcquire→addWaiter→acquireQueued(park) │ ├── 线程池 │ ├── 7参数 4拒绝策略 执行流程(核→队→非核→拒) │ ├── 配置CPU密集(N1) / IO密集(2N) │ └── 5大坑无界队列OOM/submit吞异常/混用/TL没清理/shutdown │ └── JUC工具 ├── ThreadLocalMapWeakReference → 必须remove() ├── CompletableFuturethenApply/thenCombine/allOf └── ConcurrentHashMap 1.8CASsync桶级锁多线程扩容 明日预告Day 11 — Spring 全家桶深度拆解Spring IOCBeanFactory/ApplicationContext Bean 生命周期 三级缓存循环依赖Spring AOPJDK 动态代理 vs CGLIB Transactional 失效 6 大场景Spring Boot自动配置原理 starter 机制 ConditionalSpring MVCDispatcherServlet 完整流程MyBatis核心流程 一二级缓存 $ vs #8 道 Spring 高频真题速通心法并发编程三条主线——① 锁的演进synchronized 锁升级 → ReentrantLock → AQS 体系② 线程管理生命周期 → 线程池 7 参数 → 配置公式③ 并发工具volatile/CAS → AQS → ThreadLocal → CompletableFuture。三条线分别对应怎么互斥“怎么管理”“怎么协作”串起来就是完整的并发知识体系。