《Java 100 天进阶之路》并发编程与JUC详解(第51~60篇)核心知识点汇总
并发编程与JUC详解(第51~60篇)核心知识点汇总
本阶段共10篇文章,覆盖Java并发编程从基础到精通的完整链路,是面试权重最高、区分度最大的模块之一。
第51篇:线程生命周期与创建方式
📖 查看完整文章
6种线程状态(Thread.State):
- NEW:线程创建但未启动(
new Thread()) - RUNNABLE:可运行状态(就绪 + 运行中),JVM将两者合并
- BLOCKED:阻塞状态,等待获取监视器锁(
synchronized锁被占用) - WAITING:无限期等待,需显式唤醒(
wait()、join()、park()) - TIMED_WAITING:有限期等待,超时自动唤醒(
sleep(ms)、wait(ms)、join(ms)) - TERMINATED:终止状态(
run()结束或异常退出)
4种线程创建方式:
- 继承Thread类(不推荐,单继承限制)
- 实现Runnable接口(最常用,Lambda写法)
- 实现Callable接口(有返回值,可抛异常)
- 线程池(ExecutorService)
核心对比:
- sleep() vs wait():sleep不释放锁,wait释放锁;sleep时间到自醒,wait需notify唤醒
- start() vs run():start启动新线程,run是普通方法调用
- 虚拟线程(JDK 21+):轻量级线程(几百字节),M:N调度,适合高并发I/O密集型,不适合CPU密集型
第52篇:synchronized深度解析
📖 查看完整文章
三种使用方式:
- 修饰实例方法 → 锁当前实例
this - 修饰静态方法 → 锁当前类的
Class对象 - 修饰代码块 → 锁括号内指定对象(推荐,粒度最细)
锁升级全流程(JDK 1.6+):
- 无锁→偏向锁→轻量级锁→重量级锁(单向升级,不可逆)
- 偏向锁:单线程场景,Mark Word存线程ID,无CAS开销
- 轻量级锁:轻度竞争,自旋等待(CAS),Lock Record在栈帧
- 重量级锁:重度竞争,OS互斥量,ObjectMonitor(
_owner、_count、_EntryList、_WaitSet)
Mark Word(64位JVM):
- 存储锁状态、hashCode、分代年龄、偏向线程ID
- 锁标志位:01(无锁/偏向)、00(轻量级)、10(重量级)、11(GC标记)
JVM优化:
- 自适应自旋锁(JVM动态调整自旋次数)
- 锁消除(逃逸分析移除无竞争锁)
- 锁粗化(合并连续加锁/解锁)
关键陷阱:
- JDK 15+默认禁用偏向锁(
-XX:+UseBiasedLocking可重新启用) - 虚拟线程中慎用synchronized → 导致Pinning(固定在载体线程)
第53篇:volatile与JMM
📖 查看完整文章
JMM(Java内存模型)核心:
- 抽象结构:主内存(共享变量)+ 工作内存(CPU缓存)
- JMM ≠ JVM内存结构——JMM管“怎么看见”,JVM内存结构管“存哪里”
- 三大特性:原子性(synchronized/锁)、可见性(volatile)、有序性(内存屏障)
volatile特性:
- ✅ 保证可见性(写即刷主存,读即强制重载)
- ✅ 禁止指令重排序(内存屏障)
- ❌ 不保证原子性(
i++是读-改-写三步)
内存屏障四种类型:
- StoreStore:禁止普通写重排到volatile写之前
- StoreLoad:禁止volatile写重排到后续读写之后(开销最大)
- LoadLoad:禁止volatile读后续普通读重排到前面
- LoadStore:禁止volatile读后续普通写重排到前面
Happens-Before规则:
- volatile写 → volatile读(核心规则)
- 程序顺序规则、锁规则、传递性
DCL单例必须加volatile:防止new Singleton()三步(分配内存→初始化→引用赋值)重排导致半初始化对象暴露 → NPE
第54篇:AQS抽象队列同步器
📖 查看完整文章
AQS核心三要素:
- volatile int state:同步状态(0=空闲,>0=占用/重入/许可数)
- CLH双向FIFO队列:存储等待线程(Node节点,含thread、waitStatus、prev、next)
- 模板方法模式:子类实现
tryAcquire/tryRelease等,AQS提供acquire/release骨架
两种同步模式:
- 独占模式(Exclusive):同一时刻只有一个线程能获取 → ReentrantLock
- 共享模式(Shared):同一时刻多个线程可获取 → Semaphore、CountDownLatch
Node.waitStatus:CANCELLED(1)、SIGNAL(-1)、CONDITION(-2)、PROPAGATE(-3)、0(初始)
ReentrantLock可重入实现:state计数,当前线程是持有者时state++,解锁时state--归零释放
第55篇:线程池ThreadPoolExecutor
📖 查看完整文章
七大核心参数:
| 参数 | 说明 |
|---|---|
| corePoolSize | 核心线程数(常驻员工) |
| maximumPoolSize | 最大线程数(核心+非核心) |
| keepAliveTime | 非核心线程空闲存活时间 |
| unit | 时间单位 |
| workQueue | 工作队列(BlockingQueue) |
| threadFactory | 线程工厂(自定义线程名) |
| handler | 拒绝策略 |
四步执行流程:
- 当前线程数 < corePoolSize → 创建核心线程执行
- 核心线程满 → 任务放入队列等待
- 队列满 → 当前线程数 < maximumPoolSize → 创建非核心线程
- 线程也满 → 执行拒绝策略
四种拒绝策略:
- AbortPolicy(默认):抛
RejectedExecutionException - CallerRunsPolicy:调用者线程执行(可防止任务丢失,会阻塞调用者)
- DiscardPolicy:静默丢弃新任务
- DiscardOldestPolicy:丢弃队列最旧任务,重新提交
Executors陷阱(禁止使用):
newFixedThreadPool:无界LinkedBlockingQueue → 任务堆积 → OOMnewCachedThreadPool:maxPoolSize=Integer.MAX_VALUE → 无限线程 → 资源耗尽
线程数计算公式:CPU密集型 = CPU核数 + 1;IO密集型 = CPU核数 × 2
第56篇:死锁与排查工具
📖 查看完整文章
死锁四大必要条件(Coffman条件):
- 互斥:资源一次只能被一个线程占用
- 持有并等待:持有资源的同时等待其他资源
- 不可剥夺:资源只能主动释放,不能强行剥夺
- 循环等待:线程之间形成等待环路
排查工具:
jstack -l <PID>(命令行首选):输出Found one Java-level deadlock,定位waiting to lock和locked行号jconsole(图形化):一键检测死锁VisualVM(深度分析):线程状态监控
死锁 vs 活锁 vs 饥饿:
- 死锁:永久阻塞,不主动干预永不恢复
- 活锁:不阻塞但不断重试失败,空转无推进
- 饥饿:有进展但极其缓慢(低优先级线程)
预防策略(破坏四大条件之一):
- 统一锁顺序(破坏循环等待)
ReentrantLock.tryLock(timeout)超时(破坏不可剥夺)- 一次性申请所有资源,失败则全部释放(破坏持有并等待)
- 减少锁粒度(使用ConcurrentHashMap等)
第57篇:CompletableFuture异步编程
📖 查看完整文章
Future四大痛点:
- 阻塞获取结果(
get()异步变同步) - 无法链式调用
- 无法组合多个任务
- 异常处理困难
核心API:
| 方法 | 说明 |
|---|---|
supplyAsync(Supplier) | 有返回值的异步任务 |
runAsync(Runnable) | 无返回值的异步任务 |
thenApply(Function) | 同步转换结果(同一线程) |
thenApplyAsync(Function) | 异步转换结果(新线程) |
thenCompose(Function) | 串联异步任务(扁平化) |
thenCombine | 并行合并两个任务结果 |
allOf | 等待所有任务完成(Void) |
anyOf | 等待任意一个任务完成 |
异常处理三件套:
exceptionally(Function):仅异常时触发,返回兜底值handle(BiFunction):成功/失败都触发,可继续链式处理whenComplete(BiConsumer):收尾工作,不改变结果,异常继续传播
线程池配置:
- 禁止使用
ForkJoinPool.commonPool():业务异步必须自建线程池 - CPU密集型:接近CPU核心数;IO密集型:CPU核心数×2
第58篇:原子类与CAS
📖 查看完整文章
CAS三要素:内存位置 V、预期值 A、新值 B
- CPU原子指令(x86:
lock cmpxchg),硬件保证原子性 - CAS失败 → 自旋重试(
for(;;)循环)
原子类五大分类:
- 基本类型:
AtomicInteger、AtomicLong、AtomicBoolean - 数组类型:
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray - 引用类型:
AtomicReference、AtomicStampedReference(解决ABA)、AtomicMarkableReference - 属性更新器:
AtomicIntegerFieldUpdater等(零内存开销,字段必须volatile) - 高性能累加器:
LongAdder、DoubleAdder、LongAccumulator
ABA问题:值 A→B→A,CAS误判为未被修改 →AtomicStampedReference(版本号)解决
LongAdder设计:
- 低并发:直接CAS更新
base - 高并发:分段累加
Cell[](各自累加,最后求和) @Contended防止伪共享(每个Cell独占缓存行)- 性能是AtomicLong的4倍以上
VarHandle替代Unsafe(JDK 23重要变更):
- Unsafe内存访问方法已废弃(JEP 471)
- 推荐方案:
MethodHandles.lookup().findVarHandle() - 优势:直接映射JVM字节码,类型校验,精细内存顺序控制
第59篇:ThreadLocal原理与内存泄漏
📖 查看完整文章
核心设计:空间换时间,每个线程独立变量副本,实现线程间数据隔离
JDK 1.8架构(控制权反转):
- ThreadLocal 不再维护全局Map
- 每个Thread对象持有自己的
ThreadLocalMap - ThreadLocal实例作为Map的key
Entry结构(关键设计):
- key(ThreadLocal实例):
WeakReference弱引用 - value(变量副本):强引用
内存泄漏完整链路:
ThreadLocal外部引用被置空 → GC回收ThreadLocal对象 → Entry.key变为null(弱引用特性)→ Entry.value仍是强引用无法回收 → 线程存活 → ThreadLocalMap存活 → null-key Entry永久残留 → 🔴内存泄漏关键结论:
- 弱引用只解决了ThreadLocal对象的回收(缓解泄漏),不能解决value的回收
- 根治方案:
try-finally保证remove()执行
跨线程传递方案:
InheritableThreadLocal:父子线程传递(仅新建子线程时复制)TransmittableThreadLocal(阿里开源):线程池场景上下文传递
remove() vs set(null):
remove():删除整个Entry(key+value),彻底释放内存set(null):只设value为null,Entry仍存在,内存泄漏未解决
第60篇:并发编程面试压轴题
📖 查看完整文章
本阶段共10篇,内容对应第51~59篇的全部考点,覆盖8大模块、30+高频题。
核心模块速览:
| 模块 | 核心考点 | 面试权重 |
|---|---|---|
| 基础概念 | 线程6种状态、sleep/wait、4种创建方式 | ⭐⭐⭐⭐ |
| 锁机制 | synchronized锁升级、Mark Word、ReentrantLock | ⭐⭐⭐⭐⭐ |
| JMM与volatile | 可见性、有序性、内存屏障、happens-before | ⭐⭐⭐⭐⭐ |
| AQS与并发工具 | 核心三要素、独占/共享模式、Semaphore | ⭐⭐⭐⭐⭐ |
| 线程池 | 七大参数、执行流程、拒绝策略、Executors陷阱 | ⭐⭐⭐⭐⭐ |
| 死锁排查 | 四大必要条件、jstack实战 | ⭐⭐⭐⭐ |
| 异步与无锁 | CompletableFuture编排、CAS三要素、ABA | ⭐⭐⭐⭐ |
| ThreadLocal | 内存泄漏根因、弱引用/强引用、remove | ⭐⭐⭐⭐ |