🧵 线程基础
| 创建方式 | 说明 | 示例 |
| 继承 Thread |
重写 run();Java 单继承,无法再继承其他类 |
new Thread() { public void run() {...} }.start(); |
| 实现 Runnable |
无返回值、不能抛受检异常;任务与线程解耦,推荐配合线程池 |
new Thread(() -> task()).start(); |
| Callable + FutureTask |
有返回值、可抛异常;get() 阻塞获取结果 |
new FutureTask<>(() -> 42); new Thread(ft).start(); ft.get(); |
| 线程池托管 |
复用线程、控制并发数,生产环境统一入口 推荐 |
executor.submit(() -> task()) |
| 状态(6 种) | 含义 | 进入 / 退出 |
| NEW |
已创建尚未启动 |
new Thread() 之后、start() 之前 |
| RUNNABLE |
可运行(含就绪与运行中,映射 OS 的就绪 / 运行态) |
start() 后,等待或持有 CPU 时间片 |
| BLOCKED |
等待 monitor 锁 |
进入 synchronized 未抢到锁;抢到后回到 RUNNABLE |
| WAITING |
无限期等待,需被显式唤醒 |
wait() / join() / LockSupport.park() |
| TIMED_WAITING |
限时等待,超时自动返回 |
sleep(ms) / wait(ms) / join(ms) / parkNanos() |
| TERMINATED |
终止态,生命周期结束 |
run() 执行完成或抛出未捕获异常 |
| 方法 | 所属 | 关键区别 |
| sleep |
Thread 静态方法 |
只让出 CPU 不释放锁,到时自动恢复;任意位置可调用,不响应 notify |
| wait |
Object 实例方法 |
必须在 synchronized 内调用;释放对象锁进入等待队列,由 notify / notifyAll 唤醒 |
| join |
Thread 实例方法 |
底层即 wait(0);当前线程等目标线程执行结束再继续(主线程等子线程) |
| yield |
Thread 静态方法 |
仅提示调度器让出时间片的 hint,可能立刻被再次调度;不释放锁 |
🏊 线程池
| 核心参数 | 说明 | 要点 |
| corePoolSize |
核心线程数,默认常驻不回收 |
allowCoreThreadTimeOut(true) 可让核心线程也超时回收 |
| maximumPoolSize |
最大线程数(核心 + 非核心上限) |
队列满之后才会创建非核心线程 |
| keepAliveTime |
非核心线程空闲存活时间 |
超过后空闲线程被回收,抑制线程膨胀 |
| unit |
keepAliveTime 的时间单位 |
TimeUnit.SECONDS 等 |
| workQueue |
工作队列,缓存待执行任务 |
有界 ArrayBlockingQueue / 无界 LinkedBlockingQueue / SynchronousQueue |
| threadFactory |
线程工厂,定制创建过程 |
自定义线程名便于排查问题,还可设置优先级、异常处理器 |
| handler |
拒绝策略,队列与线程全满时触发 |
默认 AbortPolicy,按业务选择或自定义 |
| 步骤 | 判定条件 | 行为 |
| 1. 核心线程 |
运行线程数 < corePoolSize |
直接创建核心线程执行(即使已有空闲线程) |
| 2. 入队 |
达到核心线程数 |
任务进入 workQueue 排队等待 |
| 3. 非核心线程 |
队列已满且线程数 < maximumPoolSize |
创建非核心线程执行队头任务 |
| 4. 拒绝 |
达到最大线程数且队列已满 |
触发拒绝策略 handler 过载 |
| 拒绝策略 | 行为 | 适用场景 |
| AbortPolicy |
直接抛出 RejectedExecutionException |
默认策略,需要感知过载并及时告警的场景 默认 |
| CallerRunsPolicy |
由提交任务的线程自己执行该任务 |
天然反压:提交线程被拖慢,生产者自动减速 不丢任务 |
| DiscardPolicy |
静默丢弃新提交的任务 |
可容忍丢失的任务,如日志埋点、监控上报 |
| DiscardOldestPolicy |
丢弃队首最老任务,腾位后重试提交 |
只关心最新数据的场景,如行情报价、实时位置 |
| 经验值 / 陷阱 | 建议 |
| CPU 密集型 |
核心线程数 = N(CPU 核数)或 N + 1,减少上下文切换开销 |
| IO 密集型 |
核心线程数 ≈ 2N,或按 N * (1 + 等待时间 / 计算时间) 估算,最终以压测定参 |
| newFixedThreadPool / newSingleThreadExecutor |
使用无界 LinkedBlockingQueue,任务持续堆积导致 OOM 陷阱 |
| newCachedThreadPool / newScheduledThreadPool |
maximumPoolSize 为 Integer.MAX_VALUE,线程无限创建导致 OOM 陷阱 |
| 为什么不用 Executors |
手动 new ThreadPoolExecutor(...),显式指定有界队列、线程名与拒绝策略,资源可控 |
提交顺序:核心线程 → 阻塞队列 → 非核心线程 → 拒绝策略
🔒 锁与同步
| synchronized 用法 | 锁对象 | 说明 |
| 修饰实例方法 |
当前对象 this |
同一对象的该方法调用互斥,不同对象互不影响 |
| 修饰静态方法 |
Class 对象 |
全局唯一,类的所有实例之间都互斥 |
| 修饰代码块 |
指定对象 synchronized(obj) |
粒度最细,推荐尽量缩小临界区减少竞争 |
| 锁升级(1.6+,单向不可逆) | 实现 | 说明 |
| 无锁 |
对象头 Mark Word 初始状态 |
尚未被任何线程竞争访问 |
| 偏向锁 |
Mark Word 记录线程 ID,同线程重入免 CAS |
只有一个线程访问时最优;撤销需等安全点,高竞争下反而拖慢(JDK 15 起默认废弃,JEP 374) |
| 轻量级锁 |
栈帧 Lock Record + CAS 自旋抢锁 |
少量线程交替、持锁时间短时,避免 OS 互斥量开销 |
| 重量级锁 |
膨胀为 monitor(OS 互斥量) |
未抢到的线程挂起阻塞,涉及用户态 / 内核态切换,开销最大 |
| 维度 | ReentrantLock | synchronized |
| 实现层面 |
JDK API 层,基于 AQS |
JVM 层 monitorenter / monitorexit 字节码指令 |
| 锁释放 |
手动 unlock(),必须放 finally |
代码块结束或异常自动释放 |
| 公平性 |
构造可选公平 / 非公平锁 |
只有非公平锁 |
| 高级功能 |
可中断 lockInterruptibly()、超时 tryLock()、多 Condition 条件队列 |
不支持中断、超时与多条件队列 |
| 性能 |
1.6 锁优化后两者基本持平 |
简单场景优先 synchronized,复杂协调再上 Lock |
| 读写锁 | 说明 | 注意 |
| ReentrantReadWriteLock |
读读共享、读写 / 写写互斥;state 高 16 位记读、低 16 位记写 |
适合读多写少;读持续占用时写线程可能饥饿 |
| StampedLock |
JDK 8+:写锁 + 悲观读 + 乐观读(不加阻塞,读后 validate(stamp) 校验版本) |
不可重入、不支持 Condition;乐观读失败需升级悲观读重试 |
⚛️ JMM 与关键字
| happens-before 规则 | 内容 |
| 程序顺序规则 |
单线程内,前面的操作 happens-before 后面的操作(保证看起来有序) |
| 监视器锁规则 |
对同一把锁,解锁 happens-before 后续的加锁 |
| volatile 规则 |
对 volatile 变量的写 happens-before 任意线程后续对它的读 |
| 线程启动规则 |
Thread.start() 之前的操作,对新线程内任意操作可见 |
| 线程终止规则 |
线程内所有操作,happens-before 其他线程检测到它结束(join() 返回 / isAlive() 为 false) |
| 中断规则 |
对线程 interrupt() 的调用,happens-before 被中断线程检测到中断 |
| 传递性 |
A HB B 且 B HB C,则 A HB C;规则可串联推理可见性 |
| volatile | 说明 |
| 可见性 |
写立即刷回主存并使其他 CPU 缓存失效,读总是加载最新值 作用一 |
| 有序性 |
内存屏障禁止指令重排;典型:DCL 单例中 new 的分配 / 初始化 / 赋值不重排 作用二 |
| 限制 |
不保证原子性:i++ 是读改写三步仍会丢更新,需 CAS 或锁 不能替代锁 |
| ThreadLocal | 说明 |
| 原理 |
每个 Thread 内持有 ThreadLocalMap,以 ThreadLocal 弱引用为 key、线程私有副本为 value,空间换隔离无竞争 |
| 内存泄漏点 |
key 是弱引用被 GC 后 Entry 变 (null, value),value 强引用仍无法回收;线程池线程长存活时持续累积 |
| 最佳实践 |
用完必须 remove()(放 finally 中);线程池里跨线程传递上下文用 TransmittableThreadLocal,解决线程复用导致的上下文错乱 |
| CAS 与 ABA | 说明 |
| CAS |
compareAndSwap 依赖 CPU 原子指令(lock cmpxchg)实现乐观并发,失败自旋重试不阻塞;JUC 的底层基座 |
| ABA 问题 |
值 A→B→A 时 CAS 感知不到中间变化;用 AtomicStampedReference 加版本戳(或 MarkableReference 布尔标记)解决 |
📦 并发容器与协作工具
| 维度 | ConcurrentHashMap 1.7 | ConcurrentHashMap 1.8 |
| 数据结构 |
Segment 分段锁数组 + HashEntry 数组 |
Node 数组 + 链表 + 红黑树(与 HashMap 对齐) |
| 锁粒度 |
锁一个 Segment(默认 16 段,并发度固定) |
锁单个桶头节点:CAS 空桶 + synchronized 非空桶 |
| size 统计 |
先无锁多次尝试统计,失败再锁全表统计 |
baseCount + CounterCell[] 分段计数(LongAdder 思路) |
| 扩容 |
Segment 内部扩容,段间独立 |
支持多线程协助迁移(transfer,ForwardingNode 标记) |
| CopyOnWriteArrayList | 说明 |
| 写时复制机制 |
写操作加锁复制新数组、修改后替换引用;读完全无锁走旧数组,读写不互斥 |
| 适用场景 |
读极多写极少:监听器列表、黑白名单、配置快照;写有复制开销且迭代器为弱一致性快照 |
| 协作工具 | 核心方法 | 适用场景 |
| CountDownLatch |
countDown() / await(),一次性倒数不可重置 |
主线程等 N 个子任务全部完成后汇总 |
| CyclicBarrier |
await(),计数归零自动重置可复用,支持屏障回调 |
N 个线程互相等齐再一起走,如分阶段并行计算 |
| Semaphore |
acquire() / release(),维护许可数 |
限流与并发资源控制,如数据库连接池配额 |
| CompletableFuture API | 说明 | 示例 |
| supplyAsync / runAsync |
提交异步任务(有 / 无返回值);默认 ForkJoinPool.commonPool,生产建议传自定义线程池 |
CompletableFuture.supplyAsync(() -> query(), bizPool) |
| thenApply / thenAccept / thenRun |
串链处理上一步结果:转换 / 消费 / 纯执行 |
cf.thenApply(r -> r * 2) |
| thenCombine / thenCompose |
合并两个独立结果 / 串联依赖的异步任务(flatMap 语义) |
f1.thenCombine(f2, (a, b) -> a + b) |
| allOf / anyOf |
等待全部 / 任一任务完成 |
CompletableFuture.allOf(f1, f2).join() |
| exceptionally / handle / whenComplete |
异常兜底:只处理异常 / 异常与结果都可转换并返回 / 仅做收尾不改变结果 |
cf.exceptionally(e -> defaultVal) |
| get vs join |
都阻塞取结果;get() 抛受检异常需 try-catch,join() 抛非受检异常写法更简洁 |
流式链路中常用 join() |
🏗️ AQS 与原子类
| AQS 核心概念 | 说明 |
| state 共享变量 |
volatile int state + CAS 修改,抽象同步资源:ReentrantLock 的重入次数、Semaphore 的许可数 |
| CLH 变体等待队列 |
抢锁失败的线程封装为 Node 入双向队列阻塞挂起;释放时头节点唤醒后继节点 |
| 独占 / 共享模式 |
独占一次只有一个线程持有(ReentrantLock);共享可多个同时获取(Semaphore、CountDownLatch、读锁) |
| 模板方法设计 |
AQS 负责排队、阻塞、唤醒;子类只需重写 tryAcquire / tryRelease / tryAcquireShared 等钩子 |
| 常用原子类 | 说明 |
| AtomicInteger / AtomicLong |
CAS 原子自增自减,计数器、序列号场景 |
| AtomicReference / FieldUpdater |
对象引用原子更新;FieldUpdater 以反射方式给普通字段补原子能力,省包装对象 |
| AtomicStampedReference |
引用 + 版本戳一起 CAS,解决 ABA 问题(MarkableReference 用布尔标记位) |
| LongAdder / LongAccumulator |
分段 Cell 累加,高并发写场景性能优先;读为近似汇总值 |
| 维度 | AtomicLong | LongAdder |
| 原理 |
单个 volatile long + CAS 自旋 |
base + Cell[] 分段:无竞争写 base,有竞争散列到各 Cell |
| 写性能 |
高竞争下 CAS 失败自旋,吞吐明显下降 |
热点分散,写吞吐随核数近线性提升 |
| 读精度 |
get() 实时精确值 |
sum() 汇总各 Cell,返回非精确的瞬时值 |
🧨 死锁现场与排查
死锁四条件:互斥、持有并等待、不可剥夺、循环等待——破坏任意一个即可预防。线上最常见的是「两把锁交叉获取」,下面是完整现场与 jstack 排查读法。
// ❌ 死锁现场:两把锁交叉获取(加锁顺序不一致 = 根因)
Object lockA = new Object(), lockB = new Object();
// 线程 1:先拿 A,再拿 B
new Thread(() -> {
synchronized (lockA) { // T1 已持有 lockA
try { Thread.sleep(100); } catch (InterruptedException ignored) {} // 制造交叉窗口,让 T2 先拿到 B
synchronized (lockB) { ... } // 等 lockB —— 被 T2 持有,BLOCKED
}
}).start();
// 线程 2:先拿 B,再拿 A(顺序与 T1 相反)
new Thread(() -> {
synchronized (lockB) { // T2 已持有 lockB
synchronized (lockA) { ... } // 等 lockA —— 与 T1 互相等待
}
}).start();
// ---- 排查:jstack <pid> 直接给出结论,重点读三处 ----
// ① 每个线程的 "waiting to lock <0x...>"(想要的锁)
// 与 "locked <0x...>"(已持有的锁),地址对号入座:
"Thread-0":
- locked <0x000000076ab621d8> (a java.lang.Object)
- waiting to lock <0x000000076ab62208> (a java.lang.Object)
"Thread-1":
- locked <0x000000076ab62208> (a java.lang.Object)
- waiting to lock <0x000000076ab621d8> (a java.lang.Object)
// ② 两个线程的锁地址互相指向对方 = 循环等待实锤
// ③ 翻到输出末尾,JVM 已自动检测并汇总:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock <0x000000076ab621d8> (a java.lang.Object),
which is held by "Thread-0"(结论:互相持有对方要的锁,按提示修复加锁顺序)
// 修复:全局统一加锁顺序(先 A 后 B),或用 tryLock(timeout) 替代无限等待
⚡ 虚拟线程速览
JDK 21 正式可用的轻量线程(Project Loom):由 JVM 调度到少量载体线程上运行,阻塞时自动让出载体,百万级并发不再是奢侈。但它不是万能替代品。
| 维度 | 说明 | 要点 |
| 适用场景 |
IO 密集 + 高并发:RPC 调用、DB 查询、HTTP 网关类请求,阻塞频繁但每次耗时短 |
任务量越大、阻塞越多,相对平台线程的收益越明显 首选 |
| 不适用场景 |
CPU 密集计算(纯占用 CPU 无阻塞,虚拟化无收益);pinning 场景:synchronized 块内阻塞或调用 native 方法时钉住载体线程 |
热点 synchronized 改 ReentrantLock 规避 pinning(JDK 24 已大幅缓解) |
| 与线程池的关系 |
不再池化:虚拟线程创建成本极低,用完即弃,一个任务一个线程 |
不要池化、不要复用;ThreadLocal 大对象会随海量线程放大内存,改用 ScopedValue |
| JDK 21+ 用法 |
Thread.ofVirtual().name("v-", 0).start(task);Spring 场景用 Executors.newVirtualThreadPerTaskExecutor() |
Boot 3.2 起 spring.threads.virtual.enabled=true 一键切换 Tomcat 虚拟线程 |