第一章:并发基础入门(必须打牢地基)

4 阅读3分钟

🚀 第一章:并发基础入门(必须打牢地基)

本章目标:搞清楚一个核心问题——为什么需要并发,以及并发到底难在哪里


一、什么是并发 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 为什么能解决线程安全?
  • 指令重排到底是怎么回事?