
"当你的服务QPS从100飙升到10000,当你的CPU使用率曲线突然飙红——那不是机器不行了,是你的线程模型塌了。" —— 某大厂P9线上事故复盘手记
2026年的今天,摩尔定律仍在生效:每18-24个月,芯片性能翻倍。但你的单线程代码没翻倍。
场景 | 单线程的结局 | 并发编程的解法 |
|---|---|---|
接口批量请求 | 串行等待,超时一片 | 多线程并行调用,响应速度提升N倍 |
日志异步打印 | 主线程卡死,用户无响应 | 独立线程异步落盘,业务零感知 |
订单异步处理 | 峰值崩溃,排队积压 | 生产者-消费者模型,削峰填谷 |
分段下载 | 一个连接慢慢磨 | 多线程分段拉取,速度拉满 |
并发编程不是选修课,是互联网架构的生存技能。 面试必考、生产必备、性能瓶颈的终极解法。
这是面试第一道送命题,也是一切并发编程的地基:
维度 | 进程(Process) | 线程(Thread) |
|---|---|---|
定位 | 系统分配资源的基本单位 | CPU调度和分派的基本单位 |
地址空间 | 独立地址空间,互不干扰 | 共享进程地址空间 |
开销 | 创建/销毁/切换,极其昂贵 | 轻量级,切换开销小得多 |
通信 | 管道、套接字,复杂 | 共享变量,简单直接 |
健壮性 | 一个崩了不影响其他 | 一个崩了可能全崩 |
比喻 | 一栋楼 | 楼里的住户 |
💡 一句话记住:进程是资源的主人,线程是干活的打工仔。一个老板(进程)可以雇多个员工(线程),共用一间办公室(地址空间),但每个员工有自己的工位(栈内存)。
搞不清状态,调试就是盲人摸象:
状态 | 含义 | 触发场景 |
|---|---|---|
NEW | 刚创建,还没start() | new Thread() |
RUNNABLE | 可运行,正在JVM中执行 | start()后,等待CPU时间片 |
BLOCKED | 阻塞于synchronized锁 | 抢锁失败 |
WAITING | 无限期等待 | Object.wait()、join()、LockSupport.park() |
TIMED_WAITING | 超时等待,到点自己回 | Thread.sleep()、wait(long) |
TERMINATED | 执行完毕,已死亡 | run()正常退出或异常终止 |
核心认知:RUNNABLE ≠ 正在跑。它可能在等CPU、等IO、等锁。真正"在跑"的线程,永远 ≤ CPU核心数。
方式 | 代码 | 推荐度 | 理由 |
|---|---|---|---|
继承Thread | class MyThread extends Thread | ⭐ | Java单继承,无法再扩展其他类,企业开发极少使用 |
实现Runnable | class MyTask implements Runnable | ⭐⭐⭐⭐⭐ | 首选:避免单继承限制,代码可共享,数据独立 |
匿名内部类 | new Thread(new Runnable(){...}) | ⭐⭐⭐ | 简洁但不够优雅 |
Lambda表达式 | new Thread(() -> {...}) | ⭐⭐⭐⭐⭐ | 现代写法,一行搞定 |
线程池 | ExecutorService pool = ... | ⭐⭐⭐⭐⭐ | 生产环境唯一推荐,后面详讲 |
java// ✅ 企业规范写法
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(() -> {
// 你的业务逻辑
});多线程访问共享变量 = 灾难。加锁?性能暴跌。
ThreadLocal的思路极其优雅:每个线程访问的都是自己的那份拷贝,天然线程安全,零锁开销。
Thread-1 → ThreadLocalMap → value₁(Thread-1专属)
Thread-2 → ThreadLocalMap → value₂(Thread-2专属)
Thread-3 → ThreadLocalMap → value₃(Thread-3专属)底层原理:
Thread对象内部有一个ThreadLocalMapThreadLocal实例本身(弱引用)实战场景 | 用法 |
|---|---|
数据库连接管理 | 每个线程持有独立连接,避免竞争 |
用户会话上下文 | 请求线程绑定当前用户信息 |
日志追踪ID | 全局透传traceId,无需方法传参 |
⚠️ 坑点:ThreadLocal是弱引用Key,GC后Key变null但Value还在 → 内存泄漏。用完必须
remove()!
javasynchronized(this) { // 对象级锁
// 临界区
}
synchronized(MyClass.class) { // 类级锁
// 临界区
}JVM自动优化三级锁升级:偏向锁 → 轻量级锁 → 重量级锁。大多数情况下,偏向锁就够了,几乎零开销。
特性 | 说明 |
|---|---|
lock() / unlock() | 手动加锁解锁,更灵活 |
tryLock(timeout) | 尝试获取,超时放弃,避免死锁 |
公平锁 / 非公平锁 | 公平锁排队,非公平锁可能插队(吞吐量更高) |
Condition | 替代wait/notify,支持多条件队列 |
锁降级 | 写锁 → 读锁,提升并发度 |
读读共享、读写互斥、写写互斥。
适合场景:读取频率 >> 写入频率(如缓存系统)。读操作可以完全并行,性能翻倍。
ReentrantLock、CountDownLatch、Semaphore、ReentrantReadWriteLock——全部基于AQS。
AQS核心思想:
int state表示资源状态acquire()失败的线程入队,release()时唤醒队首AQS = state变量 + CLH等待队列 + 模板方法模式
↓
ReentrantLock(独占) Semaphore(计数) CountDownLatch(倒计时)加锁有开销:上下文切换、CPU缓存失效、线程阻塞唤醒……
无锁方案才是高并发的王道:
方案 | 原理 | 适用场景 |
|---|---|---|
CAS(Compare-And-Swap) | 乐观锁:不加锁,更新时检查是否被改过 | 计数器、标志位 |
AtomicInteger/AtomicReference | JDK封装的CAS原子类 | 高并发计数 |
数据分段 | Hash取模,不同线程处理不同段 | 海量数据分治 |
协程 | 单线程内多任务调度,零切换开销 | Go语言、Java虚拟线程(Loom) |
java// CAS无锁计数器
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 底层CAS,无锁,性能碾压synchronized📊 实测数据:在高并发计数场景下,
AtomicInteger比synchronized快3-5倍。
永远不要在生产环境手动new Thread! 线程池才是正解。
线程池 | 特点 | 适用场景 |
|---|---|---|
newCachedThreadPool() | 可缓存,空闲回收,无界 | 大量短任务 |
newFixedThreadPool(n) | 固定数量,队列排队 | 可控并发,Web服务器 |
newSingleThreadExecutor() | 单线程,FIFO | 任务顺序执行 |
newScheduledThreadPool() | 定时/延迟执行 | 定时任务、超时回调 |
参数 | 含义 | 生产建议 |
|---|---|---|
corePoolSize | 核心线程数 | CPU密集型 ≈ 核心数 |
maximumPoolSize | 最大线程数 | IO密集型可设为核心数×2 |
keepAliveTime | 空闲线程存活时间 | 60s |
workQueue | 任务队列 | LinkedBlockingQueue(无界)或ArrayBlockingQueue(有界) |
threadFactory | 线程工厂 | 自定义命名,方便排查 |
handler | 拒绝策略 | AbortPolicy(抛异常)或CallerRunsPolicy |
提交任务 → 核心线程够?→ 是 → 直接执行
↓ 否
队列够? → 是 → 入队等待
↓ 否
非核心线程够?→ 是 → 创建执行
↓ 否
触发拒绝策略症状:CPU使用率飙红,系统态(sys)占比异常高,
cswch/s达数万次/秒
元凶:线程数 >> CPU核心数。几百个线程在4个核心上抢CPU,操作系统疯狂切换,近1/3时间在做无用功。
解决:
pidstat -w 1监控上下文切换次数四个必要条件同时满足才会死锁:
破解之道:
策略 | 操作 |
|---|---|
固定锁顺序 | 所有线程按相同顺序获取锁 |
tryLock超时 | tryLock(5, TimeUnit.SECONDS) |
避免嵌套锁 | 一次只拿一个锁 |
死锁检测 | 定期分析线程dump |
泄漏源 | 表现 | 对策 |
|---|---|---|
ThreadLocal未remove | 老年代持续增长 | finally { tl.remove(); } |
线程未关闭 | 线程数只增不减 | 线程池 + 超时机制 |
静态集合无限增长 | OOM | 弱引用 / 定时清理 |
CPU有缓存(L1/L2/L3),每个核心可能有自己的缓存副本。线程A改了变量,线程B可能读到旧值——这就是可见性问题。
JMM定义了8种原子操作:read、load、use、assign、store、write、lock、unlock,严格规定了主内存和工作内存之间的交互规则。
特性 | 说明 |
|---|---|
✅ 可见性 | 每次读取直接从主内存拿最新值 |
✅ 禁止指令重排序 | 内存屏障 |
❌ 原子性 | i++不是原子操作,仍需Atomic |
适用场景:状态标志、双重检查单例(DCL)。
判断两个操作是否有数据竞争的唯一依据:
规则 | 说明 |
|---|---|
程序次序规则 | 同一线程内,前面的先发生 |
管程锁定规则 | unlock → lock |
volatile变量规则 | 写volatile → 读volatile |
线程启动规则 | start() → 线程内所有操作 |
传递性 | A→B, B→C ⇒ A→C |
这是并发编程的皇冠明珠,也是消息队列、线程池的核心思想:
生产者 → [BlockingQueue] → 消费者
↑ ↓
满了阻塞 空了阻塞javaBlockingQueue<Task> queue = new LinkedBlockingQueue<>(1000);
// 生产者
pool.submit(() -> {
queue.put(task); // 满了自动阻塞
});
// 消费者
pool.submit(() -> {
Task t = queue.take(); // 空了自动阻塞
process(t);
});零锁、零等待、自动流控——这就是高并发架构的美学。
趋势 | 说明 |
|---|---|
虚拟线程(Project Loom) | Java 21正式商用,百万级轻量线程,协程级性能 |
Structured Concurrency | 结构化并发,自动管理线程生命周期 |
Scoped Values | 替代ThreadLocal,更安全的线程局部变量 |
无锁数据结构普及 | Disruptor、MPSC队列在高性能场景全面铺开 |
并发编程的本质,不是"让代码跑得更快",而是——
在保证正确性的前提下,让每一个CPU核心都不闲着。
从synchronized到ReentrantLock,从Thread到线程池,从CAS到虚拟线程——每一步进化,都是在跟CPU的物理极限较劲。
记住三句话:
pidstat、jstack、Arthas是你的三件套2026年,不懂并发的开发者,就像不懂SQL的DBA——还没上战场,就已经输了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。