🚀 第一章:并发基础入门(必须打牢地基)
本章目标:搞清楚一个核心问题——为什么需要并发,以及并发到底难在哪里
一、什么是并发 vs 并行(很多人第一步就搞错)
很多人把这两个概念混着用,但本质完全不同。
✅ 并发(Concurrency)
👉 同一时间段内处理多个任务
- 单核 CPU 也可以实现
- 本质是:任务切换(时间片轮转)
📌 举个例子:
线程A执行 → 切换 → 线程B执行 → 再切回A
你感觉它们“同时运行”,其实只是切得很快。
✅ 并行(Parallelism)
👉 同一时刻真正同时执行
- 必须多核 CPU
- 每个核跑一个线程
📌 举例:
CPU核1跑线程A
CPU核2跑线程B
🎯 一句话总结
| 概念 | 本质 |
|---|---|
| 并发 | 交替执行 |
| 并行 | 同时执行 |
二、进程 vs 线程(操作系统视角)
✅ 进程(Process)
👉 程序的一次执行实例
- 独立内存空间
- 资源分配单位
📌 举例:
- 打开一个浏览器 = 一个进程
✅ 线程(Thread)
👉 进程中的一个执行单元
- 共享进程内存
- 调度单位
📌 举例:
- 浏览器里多个标签页同时加载
🎯 核心区别
| 维度 | 进程 | 线程 |
|---|---|---|
| 内存 | 独立 | 共享 |
| 开销 | 大 | 小 |
| 通信 | 复杂 | 简单 |
三、Java 线程模型简介
在 Java 中:
👉 一个 Thread ≈ 一个操作系统线程
new Thread(() -> {
System.out.println("Hello Thread");
}).start();
底层:
- JVM → 调用 OS 线程
- OS → 调度 CPU 执行
四、为什么会出现线程安全问题?
👉 核心原因只有一个:
多个线程同时操作同一个共享资源
⚠️ 经典错误案例:计数器++
public class Counter {
private static int count = 0;
public static void main(String[] args) throws Exception {
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(count);
}
}
👉 你以为结果是:
20000
👉 实际可能是:
18732 / 19321 / ...
五、问题根源:count++ 到底发生了什么?
count++ 不是原子操作,它实际是:
1. 读取 count
2. +1
3. 写回 count
⚠️ 并发情况下:
线程A读取 count = 0
线程B读取 count = 0
线程A写回 1
线程B写回 1 ❌ 覆盖
👉 结果少了一次加法!
六、并发的三大核心问题(重点中的重点)
1️⃣ 原子性(Atomicity)
👉 操作要么全部完成,要么不执行
❌ count++ 不是原子操作
2️⃣ 可见性(Visibility)
👉 一个线程修改变量,其他线程能不能立刻看到?
⚠️ 示例
boolean flag = true;
new Thread(() -> {
while (flag) {}
}).start();
Thread.sleep(1000);
flag = false;
👉 可能出现:
线程永远不停止
原因:
👉 线程读的是缓存值
3️⃣ 有序性(Ordering)
👉 程序执行顺序可能被改变(指令重排)
⚠️ 示例(经典)
int a = 0;
int b = 0;
a = 1;
b = 2;
👉 实际可能执行顺序:
b = 2
a = 1
原因:
👉 编译器优化 / CPU优化
七、总结一张图(强烈建议记住)
并发问题三要素:
原子性
↑
|
可见性 ← 共享变量 → 有序性
八、为什么并发这么难?
因为你要同时面对三层问题:
| 层级 | 问题 |
|---|---|
| CPU | 指令重排 |
| 内存 | 缓存不可见 |
| 线程 | 竞争条件 |
👉 所以并发 bug:
- 很难复现 ❌
- 很难定位 ❌
- 很难测试 ❌
九、本章面试高频问题
❓1:什么是线程安全?
👉 多线程访问下,程序结果始终正确
❓2:为什么会出现线程安全问题?
👉 共享资源 + 多线程操作
❓3:count++ 为什么线程不安全?
👉 因为它不是原子操作(读-改-写)
❓4:并发的三大特性?
👉
- 原子性
- 可见性
- 有序性
🔚 本章总结(一句话带走)
并发的本质问题:多个线程对共享资源的“不可控访问”
🚀 下一章预告
👉 第二章:Java 内存模型(JMM)深入解析
你会彻底搞懂:
- volatile 为什么能保证可见性?
- synchronized 为什么能解决线程安全?
- 指令重排到底是怎么回事?