📋 概述
一句话:并发编程是为了解决"单线程处理不过来"的问题------让程序能同时做多件事,提升性能和响应速度。
从一个真实场景说起
想象一下:你开了一家网店,平时一天卖 100 单,用一个客服处理订单,绰绰有余。
突然双十一来了,同时涌入 1000 个订单。如果你还坚持"一个客服处理完一个再处理下一个",会发生什么?
- 第一个用户等了 10 分钟才拿到确认
- 第十个用户等了 100 分钟,直接退款走人
- 第一百个用户等到页面超时,再也不来了
问题的本质:单线程(单客服)的处理能力是有限的,当请求量超过处理速度时,系统就会崩溃。
解决方案 :多雇几个客服(多线程),同时处理多个订单。这就是并发编程的动机。
💡 生活类比:厨房做饭
单线程:一个人做饭
想象你一个人在厨房做饭,流程是这样的:

总耗时 :5 + 3 + 5 + 1 = 14 分钟。在这 14 分钟里,你一直在忙,但效率很低------洗菜时灶台是空的,切菜时水龙头是空的。
多线程:厨房多人协作
现在雇了三个人:你负责切菜,小王负责洗菜,小李负责炒菜。三个人分工合作:

总耗时 :可能 5 分钟 就能上好几道菜。因为三个人同时在做不同的事。
但是,多人协作也会出问题
假设你和小王都想用同一个锅:
- 你正在炒菜,小王也想炒菜
- 两个人抢一个锅,结果菜洒了,锅也坏了
这就是并发编程中的资源争抢 问题。解决方法是什么?加锁 ------"这个锅我在用,你等一下"。这就是后续章节要讲的 synchronized 和 Lock。
🔍 单线程的 3 大瓶颈
在现代计算机中,单线程程序有三个致命问题:
| 瓶颈 | 问题描述 | 生活类比 | 影响 |
|---|---|---|---|
| CPU 利用率低 | 等待 I/O(网络/磁盘/数据库)时 CPU 空转 | 厨师等外卖食材,站在厨房发呆 | 资源浪费,处理速度慢 |
| 响应性差 | 一个任务卡住,整个程序卡住 | 厨师等食材,所有客人都饿着 | 用户体验差,系统不可用 |
| 吞吐量低 | 无法利用多核 CPU | 厨房有 4 个灶台,只用 1 个 | 性能天花板低 |
代码演示:单线程的低效
arduino
public class SingleThreadDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
// 模拟 3 个耗时任务(每个 1 秒)
task("任务1", 1000);
task("任务2", 1000);
task("任务3", 1000);
long end = System.currentTimeMillis();
System.out.println("总耗时: " + (end - start) + "ms");
}
static void task(String name, long sleepTime) {
try {
System.out.println("开始执行: " + name);
Thread.sleep(sleepTime); // 模拟 I/O 等待
System.out.println("完成: " + name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
预期输出:
makefile
开始执行: 任务1
完成: 任务1
开始执行: 任务2
完成: 任务2
开始执行: 任务3
完成: 任务3
总耗时: 3000ms
问题 :3 个任务串行执行,总耗时 = 任务1 + 任务2 + 任务3 = 3 秒。在等待任务1完成时,CPU 是空闲的,但没人用它。
🔍 并发解决了什么问题
并发编程通过"同时处理多个任务"来解决单线程的瓶颈:
| 解决方案 | 原理 | 适用场景 | 效果 |
|---|---|---|---|
| 多核并行计算 | 多个 CPU 核心同时执行不同任务 | CPU 密集型(计算、加密) | 性能提升 N 倍(N=核心数) |
| 异步处理 | 主线程不等待,任务完成后回调 | I/O 密集型(网络、数据库) | 响应时间大幅降低 |
| 资源利用 | 等待时让出 CPU 给其他任务 | 混合场景 | 系统整体吞吐量提升 |
代码演示:多线程并行处理
scss
public class MultiThreadDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
// 创建 3 个线程同时执行
Thread t1 = new Thread(() -> task("任务1", 1000));
Thread t2 = new Thread(() -> task("任务2", 1000));
Thread t3 = new Thread(() -> task("任务3", 1000));
t1.start();
t2.start();
t3.start();
try {
t1.join(); // 等待 t1 完成
t2.join(); // 等待 t2 完成
t3.join(); // 等待 t3 完成
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("总耗时: " + (end - start) + "ms");
}
static void task(String name, long sleepTime) {
try {
System.out.println("开始执行: " + name);
Thread.sleep(sleepTime);
System.out.println("完成: " + name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
预期输出:
makefile
开始执行: 任务1
开始执行: 任务2
开始执行: 任务3
完成: 任务1
完成: 任务2
完成: 任务3
总耗时: 1000ms
对比 :多线程总耗时 1 秒 ,是单线程的 1/3 。因为 3 个任务同时执行。
🔍 并发的演进
Java 并发编程经历了 20 多年的演进:

| 版本 | 新增特性 | 解决的问题 |
|---|---|---|
| JDK 1.0 | Thread、synchronized |
基础多线程支持 |
| JDK 5.0 | java.util.concurrent 包 |
高效并发工具、线程池、并发集合 |
| JDK 8 | CompletableFuture、Lambda |
异步编程简化 |
| JDK 21 | 虚拟线程、结构化并发 | 轻量级线程、更好的并发控制 |
💻 可跑代码:3 个完整示例
示例 1:单线程处理(慢)
csharp
public class SlowServer {
public static void main(String[] args) {
System.out.println("=== 单线程处理 3 个请求 ===");
long start = System.currentTimeMillis();
// 模拟处理 3 个请求
handleRequest("用户A查询订单");
handleRequest("用户B提交支付");
handleRequest("用户C下载发票");
long end = System.currentTimeMillis();
System.out.println("总耗时: " + (end - start) + "ms");
System.out.println("用户平均等待: " + (end - start) / 3 + "ms");
}
static void handleRequest(String request) {
try {
System.out.println("处理: " + request);
Thread.sleep(1000); // 模拟网络 I/O
System.out.println("完成: " + request);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
预期输出:
makefile
=== 单线程处理 3 个请求 ===
处理: 用户A查询订单
完成: 用户A查询订单
处理: 用户B提交支付
完成: 用户B提交支付
处理: 用户C下载发票
完成: 用户C下载发票
总耗时: 3000ms
用户平均等待: 1000ms
示例 2:多线程并行处理(快)
csharp
public class FastServer {
public static void main(String[] args) {
System.out.println("=== 多线程处理 3 个请求 ===");
long start = System.currentTimeMillis();
// 创建 3 个线程并行处理
Thread t1 = new Thread(() -> handleRequest("用户A查询订单"));
Thread t2 = new Thread(() -> handleRequest("用户B提交支付"));
Thread t3 = new Thread(() -> handleRequest("用户C下载发票"));
t1.start();
t2.start();
t3.start();
// 等待所有线程完成
try {
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("总耗时: " + (end - start) + "ms");
System.out.println("用户平均等待: " + (end - start) / 3 + "ms");
}
static void handleRequest(String request) {
try {
System.out.println("处理: " + request);
Thread.sleep(1000);
System.out.println("完成: " + request);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
预期输出:
makefile
=== 多线程处理 3 个请求 ===
处理: 用户A查询订单
处理: 用户B提交支付
处理: 用户C下载发票
完成: 用户A查询订单
完成: 用户B提交支付
完成: 用户C下载发票
总耗时: 1000ms
用户平均等待: 333ms
示例 3:线程不安全的反例(引出后续模块)
csharp
public class UnsafeCounter {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
System.out.println("=== 线程不安全示例 ===");
// 两个线程各加 10000 次
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
count++; // 这行不是原子操作!
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
count++; // 这行不是原子操作!
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("期望值: 20000");
System.out.println("实际值: " + count);
System.out.println("差值: " + (20000 - count));
// 为什么不是 20000?
// 因为 count++ 不是原子操作,多线程下会丢失更新
// 后续章节会详细讲解 CAS 和 synchronized
}
}
预期输出(每次运行可能不同):
makefile
=== 线程不安全示例===
期望值: 20000
实际值: 15234
差值: 4766
问题分析 :count++ 看起来是一行代码,但实际上分三步:
- 读取 count 的值
- 加 1
- 写回 count
当两个线程同时执行时,可能读到相同的值,导致加 1 后覆盖对方的结果。这就是竞态条件。
⚠️ 并发的代价(预告)
多人协作虽然快,但也会出问题:
| 问题 | 描述 | 生活类比 | 后续章节 |
|---|---|---|---|
| 竞态条件 | 多个线程同时修改共享数据,结果不可预期 | 两个人同时改同一个文档 | 02_原理与实现/04_锁体系总览 |
| 死锁 | 两个线程互相等待对方释放锁,卡死 | 两个人互相让路,结果都卡在门口 | 03_问题与应对/03_并发问题排查实战 |
| 可见性问题 | 一个线程修改了变量,另一个线程看不到 | 一个人在黑板上写字,另一个人没看到 | 02_原理与实现/03_并发内存模型 |
| 线程安全 | 多线程下数据结构可能损坏 | 多人同时写账本,账本可能乱套 | 04_应用/07_并发集合详解 |
怎么解决? 后面的篇目会一一拆解:
- 锁机制(synchronized、ReentrantLock)
- 原子类(AtomicInteger、CAS)
- volatile 关键字
- 并发集合(ConcurrentHashMap)
- 线程池和同步器
ini
// 竞态条件示例:两个人同时改同一个文档
public class RaceConditionExample {
private static int balance = 100;
public static void main(String[] args) throws InterruptedException {
// 模拟两个线程同时扣款
Thread t1 = new Thread(() -> {
int temp = balance;
try { Thread.sleep(10); } catch (Exception e) {}
balance = temp - 50;
System.out.println("用户A扣款后余额: " + balance);
});
Thread t2 = new Thread(() -> {
int temp = balance;
try { Thread.sleep(10); } catch (Exception e) {}
balance = temp - 50;
System.out.println("用户B扣款后余额: " + balance);
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终余额: " + balance);
System.out.println("期望余额: 0, 实际: " + balance + " (应该为0)");
}
}
🎯 学习路径
作为专栏入口,这里告诉你接下来的模块怎么走:

| 学习阶段 | 核心内容 | 你会收获 |
|---|---|---|
| 入门 | 线程基础、内存模型、volatile、synchronized | 理解并发的基本概念,能写简单的线程安全代码 |
| 进阶 | 锁机制、AQS、线程池、CompletableFuture | 看懂 JUC 源码,会设计并发方案 |
| 实战 | 死锁排查、设计模式、虚拟线程 | 能排查线上问题,能设计并发架构 |
💡 面试要点
-
为什么用多线程?
- 提升性能:多核 CPU 并行计算
- 提升响应:I/O 等待时 CPU 做其他事
- 提升吞吐:同时处理更多请求
-
并发 vs 并行区别?
- 并发:多个任务交替执行(单核也能做到)
- 并行:多个任务同时执行(需要多核)
-
并发的代价?
- 竞态条件:数据不一致
- 死锁:线程互相等待
- 可见性:修改不可见
- 复杂度:代码更难写、更难测
-
单线程 vs 多线程性能对比?
- CPU 密集型:多线程 ≈ 单线程(甚至更慢,因为线程切换开销)
- I/O 密集型:多线程 >> 单线程(等待时做其他事)
-
如何避免线程安全问题?
- 加锁:synchronized、ReentrantLock
- 原子类:AtomicInteger、CAS
- volatile:保证可见性
- 并发集合:ConcurrentHashMap
📝 总结
| 要点 | 记住这一句 |
|---|---|
| 动机 | 单线程处理不过来,需要多线程并行 |
| 类比 | 一个人做饭 vs 厨房多人协作 |
| 瓶颈 | CPU 空转、响应差、吞吐低 |
| 解决 | 多核并行、异步处理、资源复用 |
| 代价 | 竞态、死锁、可见性问题 |
| 演进 | Thread → JUC → 虚拟线程 |
| 下一步 | 学线程基础,理解线程是什么 |
一句话总结:并发编程是为了解决"慢"的问题,但引入了"乱"的问题------后面的章节会教你如何"又快又稳"。