阶段06-多线程与并发编程.md 12 KB

阶段六:多线程与并发编程 —— 核心知识点总纲

本阶段涵盖:多线程入门、线程操作与线程安全、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()

Thread t = new Thread(new RunnableImpl(), "光头强线程!");
t.start();

为什么更推荐: 不占单继承位、可多实现接口、任务与线程解耦(组合优于继承)。 三种提供方式:实现类对象 / 匿名内部类 / Lambda(Runnable 是函数式接口)。

方式三:实现 Callable + FutureTask(可返回结果) 步骤:①implements Callable<V> ②重写 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 时锁永不释放 → 死锁。

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. 使用示例

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 参数 + 流程:核心 → 队列 → 非核心 → 拒绝