Java 多线程与并发编程学习笔记
适用场景:后端开发面试、高并发系统设计
核心逻辑:基础语法 -> 线程安全 -> 资源复用(线程池) -> 分布式架构
1. 基础篇:生命周期与创建
1.1 创建线程
-
继承
Thread类:❌ 不推荐。Java 是单继承的,继承后无法扩展其他类。 -
实现
Runnable接口:✅ 推荐。实现解耦(任务与执行分离),方便后续配合线程池使用。
1.2 关键状态流转(高频考点)
-
RUNNABLE:包含
Ready(就绪) 和Running(运行中)。 -
WAITING / TIMED_WAITING:
-
定义:线程主动让出 CPU,进入等待队列(不消耗 CPU)。
-
触发:
wait(),join(),LockSupport.park()。 -
注意:这不是忙等(Busy-Wait),忙等通常发生在 CAS 自旋中。
-
-
BLOCKED:
-
定义:线程被动阻塞。
-
触发:特指争抢
synchronized锁失败,被挡在门外排队。
-
2. 安全篇:锁与原子性
2.1 悲观锁:synchronized
-
原理:基于 Monitor(监视器锁)。
-
锁的范围:
-
synchronized(this)/ 实例方法 $\rightarrow$ 锁当前对象。 -
synchronized(Class)/ 静态方法 $\rightarrow$ 锁类对象(全局互斥)。
-
-
特点:竞争失败的线程会挂起(Block),让出 CPU。适合写多读少、竞争激烈的场景。
2.2 可见性:volatile
-
作用:禁止 CPU 缓存优化,保证变量修改后立即写回主内存,对其他线程可见。
-
局限:✅ 保证可见性,❌ 不保证原子性(无法解决
count++问题)。
2.3 乐观锁:CAS (Compare-And-Swap)
-
实现:
AtomicInteger等原子类。 -
原理:无锁编程。只有当内存值符合预期时才修改。
-
缺点:自旋 (Spinning)。如果竞争太激烈,线程会一直
while循环重试,导致 CPU 飙升。适合读多写少、竞争较小的场景。
3. 实战篇:线程池 (Thread Pool)
3.1 核心参数 (银行模型)
-
corePoolSize:常驻核心线程。
-
workQueue:阻塞队列(候客区)。
-
maximumPoolSize:最大线程数(含临时工)。
-
handler:拒绝策略。
3.2 拒绝策略 (4种)
当队列满且线程数达到最大时触发:
-
AbortPolicy:直接抛异常(默认)。 -
CallerRunsPolicy:谁提交谁执行(主线程自己干)。✅ 推荐,能起到反压/限流作用。 -
DiscardPolicy:默默丢弃,不通知。 -
DiscardOldestPolicy:喜新厌旧,踢掉队列里排队最久的任务,接纳新任务。
3.3 生产大忌 (OOM 风险)
-
🚫 严禁使用:
Executors.newFixedThreadPool() -
原因:它使用
LinkedBlockingQueue且默认容量为Integer.MAX_VALUE(无界)。 -
后果:请求堆积导致内存耗尽,抛出 OOM (OutOfMemoryError)。
4. 架构篇:分布式锁 (Redis)
场景:多台服务器(集群)抢占同一资源(如秒杀),JVM 锁失效。
4.1 演进三步曲
-
防死锁:必须设置过期时间 (TTL)。
-
命令:
SET key value NX EX 10(保证原子性)。
-
-
防误删:A 任务太慢,锁过期被 B 抢走;A 完事后误删了 B 的锁。
-
解法:Value 存入 UUID。删除前判断
if (value == myUUID)(需用 Lua 脚本保证原子性)。
-
-
业务超时 (看门狗 Watchdog):
-
问题:业务没跑完锁就过期了。
-
解法:使用 Redisson。后台有线程定时检查,若业务还在运行,自动续期。
-
5. 🎯 面试 Q&A 核心复盘
Q1: 为什么要用 Runnable 而不是继承 Thread?
-
核心点:Java 是单继承的。使用 Runnable 接口更灵活,且符合“任务与执行分离”的解耦原则。
Q2: 线程的 WAITING 和 BLOCKED 状态有什么本质区别?
-
修正点:
-
WAITING:是自愿的(如调用
wait()),线程进入等待队列,不占用 CPU(不是忙等)。 -
BLOCKED:是被迫的,因为抢不到 synchronized 锁而被挂起。
-
误区提醒:不要把 WAITING 说成是“忙等”或“自旋”,那是 CAS 的行为。
-
Q3: volatile 能保证线程安全吗?
-
核心点:不能。它只能保证可见性,不能保证原子性。多线程并发写依然会出现竞态条件。
Q4: 线程池队列满了会发生什么?(OOM 问题)
-
核心点:如果是无界队列(
newFixedThreadPool),会无限制接收任务直到 内存爆满 (OOM)。如果是有界队列,满了之后会尝试创建非核心线程,如果还满则触发拒绝策略。
Q5: 高并发抢购,选 Synchronized 还是 CAS?
-
核心点:选 Synchronized (悲观锁)。
-
理由修正:
-
CAS (乐观锁) 在高竞争下会产生大量自旋 (Spinning),也就是空转循环,严重浪费 CPU 资源。
-
Synchronized 在高竞争下会升级为重量级锁,让抢不到锁的线程阻塞 (Block/Sleep),挂起时不消耗 CPU,反而更高效。
-
Q6: Redis 分布式锁如果不加“看门狗”会有什么后果?
-
核心点:锁提前过期 + 误删锁。
-
业务没跑完锁就没了 -> 导致并发超卖(锁失效)。
-
前一个线程结束去删锁 -> 删掉了后一个线程刚抢到的锁(连锁反应)。
-
更多推荐
所有评论(0)