知识点深化 · Java · 线程并发
线程与并发:synchronized/volatile/锁/线程池
多线程提高吞吐,但共享数据会打架。synchronized 保证原子性可见性,volatile 保证可见性不保证原子性,线程池复用线程控资源。这一页把锁、volatile、JMM、线程池参数讲透。
① 小白第一课怎么学(4 步走,约 60 分钟)
别急着背代码,先按这四步建立直觉:
1看图建立直觉(10 分钟)
读②③:多个线程同时改一个变量会怎样。
2记锁与 JMM(15 分钟)
读④:synchronized、volatile、线程池。
3手写并发(20 分钟)
精读⑤。
4刷题纠错(15 分钟)
做⑦⑩,错题回⑥。
本课小目标学完你要能:① 区分 synchronized 和 volatile;② 说出线程池 7 参数;③ 理解可见性/原子性/有序性。
② 一图看懂:并发全地图
读法:中心是多线程并发,上三大特性,中锁与 volatile,下线程池与易错。
③ 本质直觉:多线程改同一变量,就像多人同时改一份文档
问题出在哪:每个线程有自己的工作内存,改共享变量时先拷贝到本地、改完再写回主内存。两个线程同时 i++,各加各的,最后少加一次——这就是原子性被破坏。
synchronized:同一时刻只让一个线程进临界区,既保证互斥(原子性),又保证可见性(改完刷主内存)。
volatile:只保证变量改了立刻对其他线程可见(可见性)、禁止指令重排,但不保证 i++ 这种复合操作的原子性。
线程池:不用每次 new Thread,用池子复用线程、控制并发数、管理队列。
三大特性原子性(操作不可分割)、可见性(一个改了别人立刻看到)、有序性(禁止指令重排)。synchronized 全包,volatile 只管可见+有序。
④ 完整体系与对比表
synchronized vs volatile
| 对比 | synchronized | volatile |
| 原子性 | 保证(互斥) | 不保证 |
| 可见性 | 保证 | 保证 |
| 有序性 | 保证 | 保证(禁重排) |
| 阻塞 | 会阻塞 | 不阻塞 |
| 适用 | 复合操作/临界区 | 状态标志位、一写多读 |
线程池 7 参数
ThreadPoolExecutor 构造corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler
任务来了:核心线程满 → 进队列 → 队列满 → 开非核心线程到 max → 满了执行拒绝策略。
拒绝策略:AbortPolicy(抛异常,默认)、CallerRunsPolicy(调用者跑)、DiscardPolicy、DiscardOldestPolicy。
⑤ 用法场景与典型例题
例1(volatile)volatile int i; 多线程 i++ 还会丢吗?
i++ 是读-改-写三步。
volatile 只保证可见,不保证原子。两个线程同时读到 i=0,各写回 1,照样丢。答案:仍会丢,要加锁或 AtomicInteger。
例2(线程池)核心 2、最大 4、队列 100,来 3 个任务,开几个线程?
没满核心用核心,队列没满进队列。
3 ≤ core(2)? 先开 2 核心线程,第 3 个进队列。答案:2 个线程,1 个排队。
例3(可见性)一个线程改 boolean flag,另一个死循环读 flag,为什么停不下来?
工作内存缓存了旧值。
读线程把 flag 缓存在工作内存,看不到主线程的修改。加 volatile 强制每次读主内存即可。
做题心法复合操作(i++、check-then-act)用 synchronized/锁;纯标志位一写多读用 volatile。
⑥ 高频错误诊断(4 条)
错误1:以为 volatile 能让 i++ 线程安全i++ 是三步复合操作,volatile 不保证原子,仍会丢。用 AtomicInteger 或加锁。
错误2:用 Executors 快捷创建线程池FixedThreadPool/CachedThreadPool 队列/线程无界,可能 OOM。手动用 ThreadPoolExecutor 设界。
错误3:同步方法锁错对象synchronized 锁 this 是对象锁,不同对象不互斥;要锁类用 static synchronized 或类对象。
错误4:notify 不 await/while 循环唤醒后要重新检查条件(while 而不是 if),防止虚假唤醒。
⑦ 考点真题演练(4 题)
考点分布
| 考法 | 出题形式 | 应对 |
| 锁 vs volatile | volatile 保不保证原子 | 不保证 |
| 线程池 | 任务提交流程 | 核心→队列→max→拒绝 |
| 可见性 | flag 不可见怎么办 | 加 volatile |
| 拒绝策略 | 四种 | Abort/CallerRuns/Discard |
真题基础1. volatile 关键字能保证的是?
真题中档2. volatile 修饰的变量 i,多线程执行 i++ 会?
真题中档3. ThreadPoolExecutor 任务提交,当核心线程和队列都满了会?
真题拔高4. synchronized 不具备下列哪项?
⑧ 必背知识点卡
原子性:操作不可分割 i++ 不原子
可见性:改了别人立刻看到 volatile
有序性:禁指令重排 volatile/synchronized
synchronized:互斥+可见+有序,会阻塞
volatile:只可见+有序,不原子 标志位
线程池:核心→队列→max→拒绝 7 参数
拒绝策略:Abort/CallerRuns/Discard
⑨ 应用输出:设计一个异步下单的线程池
场景:订单服务要异步发短信、加积分,不能阻塞主流程。
① 选型:手动 new ThreadPoolExecutor,核心 4、最大 8、有界队列 200,拒绝策略 CallerRuns。
② 为什么不用 Executors:FixedThreadPool 队列无界会 OOM,手动设界可控。
③ 提交任务:下单成功后把发短信/积分任务 submit 到线程池,主流程不等。
④ 拒绝策略:CallerRuns 让主线程自己跑,起到背压不丢任务。
⑤ 监控:打印队列长度、活跃线程,满了告警扩容。
口述思路合上书说:"异步任务用有界线程池,核心队列最大都设界,拒绝策略选 CallerRuns。"
⑩ 分层练习(基础 + 中档 + 拔高)
▍基础 6 题
基础1三大特性是什么?
原子性、可见性、有序性。
基础3synchronized 会阻塞吗?
会,互斥。
基础4线程池核心参数?
corePoolSize、maxPoolSize、队列等 7 个。
基础5线程池默认拒绝策略?
AbortPolicy 抛异常。
基础6i++ 是原子操作吗?
不是,读-改-写三步。
▍中档 6 题
中档7为什么不能用 Executors.newFixedThreadPool?
队列无界,任务堆积会 OOM。
中档8synchronized 锁普通方法锁的是谁?
当前对象 this。
中档9状态标志位用 volatile 还是锁?
一写多读标志位用 volatile 更轻。
中档10线程池队列满且线程达 max 怎么办?
触发拒绝策略。
中档11AtomicInteger 怎么实现原子?
CAS 乐观自旋,无锁。
中档12wait/notify 用在什么对象?
必须在 synchronized 块内,锁对象上调用。
▍拔高 6 题
拔高13CAS 是什么?缺点?
比较并交换乐观锁;缺点自旋开销、ABA 问题。
拔高14synchronized 锁升级过程?
无锁→偏向锁→轻量级锁→重量级锁。
拔高15什么是 ThreadLocal?
线程本地变量,各线程各一份,注意内存泄漏。
拔高16线程池怎么合理设核心数?
CPU 密集≈核数+1;IO 密集≈核数×(1+等待/计算)。
拔高17happens-before 是什么?
JMM 偏序规则,前一个操作结果对后一个可见。
拔高18死锁四个必要条件?
互斥、持有并等待、不可剥夺、循环等待。
⑪ 记忆口诀 + 7 天复习计划
三句口诀① 原子可见有序三特性,synchronized 全包 volatile 只见可见。② i++ 不原子要加锁,标志位才用 volatile。③ 线程池设界别用 Executors,核心队列最大拒绝。
| 天 | 任务 | 自检 |
| 第 1 天 | 读②③④,画 i++ 丢更新 | 能说清问题 |
| 第 2 天 | 背锁对比 + 基础 1-6 | volatile 边界对 |
| 第 3 天 | 做中档 7-12,写线程池参数 | 流程对 |
| 第 4 天 | 做拔高 13-18,讲 CAS 与锁升级 | 能讲清 |
| 第 5 天 | 做⑦真题 4 题 | 限时每题 2 分钟 |
| 第 6-7 天 | 合上书默写线程池流程 | 不看资料全默对 |
← 返回软件技术总览