# 阶段六:多线程与并发编程 —— 核心知识点总纲 > 本阶段涵盖:**多线程入门、线程操作与线程安全、volatile/Lock 锁/线程池** > 细分文档:`按日细分/20260808-笔记.md`(多线程入门部分)、`按日细分/20260810-笔记.md`、`按日细分/20260811-笔记.md` > 学生需要重点理解、背诵、掌握的理论核心知识点。 --- ## 一、多线程入门(对应 08-08) ### 1. 基本概念(必背) | 概念 | 说明 | |------|------| | **程序** | 硬盘中的文件,静态代码 | | **进程** | **内存中正在执行的程序**,占用 CPU/内存等资源 | | **线程** | **进程中的单个顺序控制流**,一条执行路径;一个进程可含多线程 | - **main 方法 = 主线程** - 进程三大特性:独立性、动态性、并发性 ### 2. 并行 vs 并发(必背) | 概念 | 含义 | 类比 | |------|------|------| | **并行** | 同一时刻多指令在**多个 CPU** 上同时执行 | 3 灶台 3 厨师 | | **并发** | 同一时刻多指令在**单个 CPU** 上交替执行 | 3 灶台 1 厨师 | ### 3. 实现多线程的三种方式(必背) **方式一:继承 Thread** 步骤:①继承 Thread ②重写 run() ③创建对象 ④start() 启动 - **run()**:封装被线程执行的代码;**直接调用不创建新线程** - **start()**:**启动新线程**,由 JVM 调用 run() **方式二:实现 Runnable(更推荐)** 步骤:①implements Runnable ②重写 run() ③创建对象 ④`new Thread(对象, "线程名")` ⑤start() ```java Thread t = new Thread(new RunnableImpl(), "光头强线程!"); t.start(); ``` **为什么更推荐:** 不占单继承位、可多实现接口、任务与线程**解耦**(组合优于继承)。 三种提供方式:实现类对象 / 匿名内部类 / **Lambda**(Runnable 是函数式接口)。 **方式三:实现 Callable + FutureTask(可返回结果)** 步骤:①implements Callable\ ②重写 call() ③创建对象 ④`new FutureTask<>(callable)` ⑤`new Thread(futureTask)` ⑥start() ⑦`get()` 获取结果 - `call()` **带返回值**(对比 run() 无返回值) - `FutureTask` 是"中间人";`get()` 阻塞等待计算结果 **三种方式对比:** | 维度 | 继承 Thread | 实现 Runnable | 实现 Callable | |------|------------|--------------|---------------| | 返回结果 | 无 | 无 | **有** | | 扩展性 | 差 | 强 | 强 | | 直接用 Thread 方法 | 可以(this 是线程) | `Thread.currentThread()` | 同左 | | 推荐 | 入门 | 更推荐 | 需返回结果 | **Thread 常用方法:** `setName/getName`、`Thread.currentThread()`(静态,返回当前线程对象) ## 二、线程操作(对应 08-10) ### 1. sleep 与线程调度 - **`Thread.sleep(毫秒)`**:静态方法,当前线程休眠(抛 InterruptedException) - **线程调度**:分时调度(轮流均分)vs **抢占式调度(Java 采用)**——高优先级机会多 - **执行随机性**:谁抢到 CPU 时间片不确定 → 多线程输出顺序随机 ### 2. 线程优先级 `getPriority()`(默认 5)/ `setPriority(int)`(范围 1~10) ### 3. 守护线程(必背) - **守护线程**随其他非守护线程(用户线程)结束而结束 - **JVM 退出条件**:所有用户线程都结束才退出;守护线程不阻碍退出;**main 结束 ≠ JVM 退出** - `setDaemon(true)` 必须在 `start()` **之前**,否则 IllegalThreadStateException - 守护线程创建的子线程默认也是守护线程 - **finally 不保证**:JVM 强杀时 finally 不一定执行,不能用于资源清理 - 经典场景:GC 线程、心跳检测、后台日志 ### 4. 线程 6 种状态(必背) | 状态 | 枚举 | 触发 | |------|------|------| | 新建 | NEW | new Thread() 未 start() | | 可运行 | RUNNABLE | start() 后运行或等时间片 | | 阻塞 | BLOCKED | 抢同步锁失败 | | 等待 | WAITING | wait() / join() / park() | | 计时等待 | TIMED_WAITING | sleep(ms) / wait(timeout) / join(timeout) | | 终止 | TERMINATED | run() 结束(终点站) | - **RUNNABLE 是总枢纽**(阻塞状态都能回到 RUNNABLE);**TERMINATED 不可逆** - OS 5 状态(就绪/运行分开)vs Java 6 状态(就绪+运行合并为 RUNNABLE) ### 5. 线程操作方法(必背) | 方法 | 作用 | |------|------| | `join()` / `join(ms)` | 当前线程等待目标线程结束 / 限时等待 | | `interrupt()` | **设置中断标志**(不杀线程);sleep/wait/join 中抛异常并清标志 | | `yield()` | 主动让出 CPU(只是**建议**) | | `isAlive()` | 判断线程是否存活 | | `wait()` / `notify()` / `notifyAll()` | **Object 类**方法:等待/唤醒(需持有锁) | **interrupt 机制**:只设标志、请求线程自行响应;`isInterrupted()` 主动检查;catch 后**重设标志**(`Thread.currentThread().interrupt()`)。 ## 三、线程安全(对应 08-10) ### 1. 数据竞争 线程间**共享数据和内存**。多线程同时读写同一共享变量 → 结果不可预测、错误无法复现。 ### 2. 两个经典 Bug - **卖票问题**:相同票多次出现(同时通过 if 判断)+ 负数票(tickets-- 连续执行) - **计数器问题**:`count++` 不是原子操作(**读-改-写 3 步**),理论 1000 实际小于 1000 ### 3. 线程安全 3 原因(缺一不可) 1. **多线程环境**(至少两条路径并发) 2. **存在共享数据** 3. **读-改-写复合操作**(count++、tickets--) ### 4. 线程安全三大特性(必须背) | 特性 | 含义 | 解决方案 | |------|------|----------| | **原子性** | 操作不可分割 | synchronized、Lock、Atomic 类 | | **可见性** | 修改对另一线程立即可见 | volatile、synchronized、Lock | | **有序性** | 代码按书写顺序执行 | volatile、synchronized | > **口诀"原可序"**:原子性、可见性、有序性。 ### 5. synchronized(必背) - Java 内置关键字,保证**原子性最简单可靠**的手段 - **每个对象底层对应一把监视器锁(Monitor Lock)** - 拿不到锁 → 进入 **BLOCKED** 排队;执行完自动释放锁 - **锁粒度尽量小**(只锁共享数据代码,不锁整个方法) **三种写法:** | 写法 | 锁的是什么 | |------|-----------| | 同步实例方法 `synchronized void m()` | 当前对象 **this** | | 同步静态方法 `static synchronized void m()` | 类的 **Class 对象** | | 同步代码块 `synchronized(lock)` | **指定的任意对象**(最灵活) | **转账场景**:"检查-修改"必须整体锁住(`if (balance >= amount)` + `balance -= amount` 用 synchronized(this) 包住)。 ## 四、volatile 与 JVM 内存模型(对应 08-11) ### 1. volatile 三大语义(必背) 1. **可见性**:写 volatile 立即刷主存;读强制从主存读(禁止用 CPU 缓存) 2. **禁止重排序(有序性)**:volatile 读写及前后指令不重排 3. **不保证原子性**:`volatile count++` 依旧丢更新——必须用 synchronized/Lock/原子类 ### 2. 可见性问题根源(JVM 内存模型) - 每线程有**独立工作内存(CPU 缓存)**,读写优先走工作内存 - 刷新主存有延迟 → main 改 running 为 false,worker 一直读缓存旧值 true → **while 永不退出** - volatile 解决"程序永不退出" ### 3. volatile vs synchronized | 对比 | volatile | synchronized | |------|----------|--------------| | 本质 | 轻量、不加锁 | 锁机制(Monitor Lock) | | 解决 | 可见性 + 有序性 | **三大特性全部** | | 适用 | 标志位 | 复合操作 | > **验证**:volatile 修饰 count 后 count++ 结果照样 < 1000(只解决可见性);加 synchronized 后必然 = 1000。 ## 五、Lock 锁(对应 08-11) ### 1. ReentrantLock 可重入锁 **JDK 1.5** 提供 `Lock` 接口 + `ReentrantLock` 实现,比 synchronized 更精细: 1. **超时等待**(tryLock(timeout)) 2. **响应中断**(lockInterruptibly()) 3. **指定公平性**(`new ReentrantLock(true)` 公平锁) 4. 多条件变量、支持锁读(读写分离) ### 2. 使用铁律(必背) > **synchronized 自动释放锁;Lock 必须手动 unlock() 且放 finally**——否则抛异常/return 时锁永不释放 → 死锁。 ```java lock.lock(); try { count++; } finally { lock.unlock(); } ``` ### 3. 核心方法 `lock()` / `unlock()` / `tryLock()`(拿不到立即返回 false)/ `tryLock(timeout, unit)`(限时)/ `lockInterruptibly()`(可中断) ### 4. synchronized vs Lock 七维对比(面试常考) | 维度 | synchronized | ReentrantLock | |------|--------------|---------------| | 语法 | 简单(关键字) | 复杂(try-finally) | | 释放 | **自动** | **手动 unlock** | | 可中断 | 不支持 | 支持 | | 超时等待 | 不支持 | 支持 | | 锁读 | 不支持 | 支持(读写分离) | | 底层 | **监视器锁 Monitor Lock** | **AQS 抽象队列同步器** | | JDK21 | 大幅优化 | 性能相近 | ## 六、死锁(对应 08-11) **概念**:两个或多个线程**相互持有对方所需的资源**,导致等待无法继续。 ### 4 个必要条件(缺一不可,必背) 1. **互斥**:资源同一时刻只能被同一线程占用 2. **持有且等待**:持有一个资源同时等另一个 3. **不可剥夺**:已持资源不能强夺,只能自己释放 4. **循环等待**:你等我、我等你形成环 ### 避免死锁 3 方法 | 方法 | 打破的条件 | |------|-----------| | **固定锁顺序**(所有线程按相同顺序加锁) | 打破"循环等待" | | **tryLock() 超时获取**(拿不到就放弃) | 打破"持有且等待" | | **高级并发工具**(如 ConcurrentHashMap) | 从源头避免手动加多把锁 | > 排查死锁:`jstack` 命令找 `Found one Java-level deadlock`。 ## 七、线程池(对应 08-11) ### 1. new Thread().start() 的三个问题(必背) 1. **创建/销毁开销大**(每线程 1MB 栈内存 + OS 交互) 2. **线程数量不受控**(高并发耗尽资源崩溃) 3. **资源利用率低**(任务几毫秒、创建销毁几秒) **解决方案:【线程复用 + 任务排队 + 动态扩容 + 上限保护】** ### 2. ThreadPoolExecutor 7 个参数(必背) | 参数 | 说明 | |------|------| | corePoolSize | 核心线程数(常驻) | | maximumPoolSize | 最大线程数(必须 > 核心) | | keepAliveTime | 非核心线程空闲存活时间 | | unit | 时间单位 | | workQueue | 工作队列 | | threadFactory | 线程工厂 | | handler | 拒绝策略 | ### 3. 执行流程(必背) **核心线程 → 工作队列 → 非核心线程 → 拒绝策略** > 线程不够**不先创建新线程**,先放工作队列;**队列满且未达最大线程数**才扩容。 ### 4. 拒绝策略 `AbortPolicy`(默认抛异常)/ `CallerRunsPolicy`(调用者执行)/ `DiscardPolicy`(丢弃)/ `DiscardOldestPolicy`(丢最旧) ### 5. 使用示例 ```java ThreadPoolExecutor pool = new ThreadPoolExecutor( 3, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); pool.execute(runnable); // 复用线程,不是 new Thread pool.shutdown(); // 关闭线程池 ``` > ⚠️ 线程池解决"创建开销"问题,**不解决"数据竞争"问题**(池中线程处理共享数据仍需锁)。 ## 本阶段必须背诵的核心口诀 1. **程序/进程/线程**:硬盘文件 / 内存执行的程序 / 进程中的执行路径 2. **并行 = 多 CPU 同时;并发 = 单 CPU 交替** 3. **run() 封装任务、start() 启动线程;实现 Runnable 比继承 Thread 更推荐** 4. **Callable 有返回值(call)+ FutureTask 包装 + get() 取结果** 5. **守护线程随用户线程结束;JVM 只有用户线程全结束才退出** 6. **线程 6 状态**:NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED(终点) 7. **线程安全三原因**:多线程 + 共享数据 + 读改写(缺一不可) 8. **三大特性"原可序"**:原子性→锁/原子类;可见性→volatile/锁;有序性→volatile/synchronized 9. **volatile 解决可见性+有序性,不保证原子性** 10. **Lock 必须手动 unlock 放 finally;支持超时/可中断/公平锁** 11. **死锁 4 条件**:互斥、持有且等待、不可剥夺、循环等待 12. **线程池 7 参数 + 流程**:核心 → 队列 → 非核心 → 拒绝