← 返回软件技术总览 软件技术 · 知识点深化 · 线程与并发:synchronized/volatile/锁/线程池
知识点深化 · Java · 线程并发

线程与并发:synchronized/volatile/锁/线程池

多线程提高吞吐,但共享数据会打架。synchronized 保证原子性可见性,volatile 保证可见性不保证原子性,线程池复用线程控资源。这一页把锁、volatile、JMM、线程池参数讲透。

① 小白第一课怎么学(4 步走,约 60 分钟)

别急着背代码,先按这四步建立直觉:

1看图建立直觉(10 分钟)
读②③:多个线程同时改一个变量会怎样。
2记锁与 JMM(15 分钟)
读④:synchronized、volatile、线程池。
3手写并发(20 分钟)
精读⑤。
4刷题纠错(15 分钟)
做⑦⑩,错题回⑥。
本课小目标学完你要能:① 区分 synchronized 和 volatile;② 说出线程池 7 参数;③ 理解可见性/原子性/有序性。

② 一图看懂:并发全地图

多线程并发 三大特性 原子/可见/有序 synchronized 互斥+可见+原子 volatile 只保证可见/禁重排 线程池 7 参数/复用线程 JMM:主内存/工作内存 happen-before 易错:volatile 不保证原子 i++ 仍要加锁
读法:中心是多线程并发,上三大特性,中锁与 volatile,下线程池与易错。

③ 本质直觉:多线程改同一变量,就像多人同时改一份文档

问题出在哪:每个线程有自己的工作内存,改共享变量时先拷贝到本地、改完再写回主内存。两个线程同时 i++,各加各的,最后少加一次——这就是原子性被破坏。

synchronized:同一时刻只让一个线程进临界区,既保证互斥(原子性),又保证可见性(改完刷主内存)。

volatile:只保证变量改了立刻对其他线程可见(可见性)、禁止指令重排,但不保证 i++ 这种复合操作的原子性。

线程池:不用每次 new Thread,用池子复用线程、控制并发数、管理队列。

i++ 被并发改写丢 主内存 i=0 线程A工作内存i=0→1 线程B工作内存i=0→1 两个都写回 i=1,本该是 2 synchronized 串行化,volatile 只见可见
三大特性原子性(操作不可分割)、可见性(一个改了别人立刻看到)、有序性(禁止指令重排)。synchronized 全包,volatile 只管可见+有序。

④ 完整体系与对比表

synchronized vs volatile

对比synchronizedvolatile
原子性保证(互斥)不保证
可见性保证保证
有序性保证保证(禁重排)
阻塞会阻塞不阻塞
适用复合操作/临界区状态标志位、一写多读

线程池 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 volatilevolatile 保不保证原子不保证
线程池任务提交流程核心→队列→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三大特性是什么?
原子性、可见性、有序性。
基础2volatile 保证原子吗?
不保证。
基础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-6volatile 边界对
第 3 天做中档 7-12,写线程池参数流程对
第 4 天做拔高 13-18,讲 CAS 与锁升级能讲清
第 5 天做⑦真题 4 题限时每题 2 分钟
第 6-7 天合上书默写线程池流程不看资料全默对

← 返回软件技术总览