Java 并发深度话题
线程池 7 参数 · synchronized vs Lock · volatile · CAS/AQS · ThreadLocal(详见专题 22 辨析)
🎬 怎么引出
「Java 并发我踩过真实坑 — 之前用 Executors.newFixedThreadPool,阿里规约禁止,因为用的无界队列 LinkedBlockingQueue,任务堆积 OOM。改成自己 new ThreadPoolExecutor 配有界队列 + 拒绝策略。还有 ThreadLocal 在线程池里用完没 remove 导致串数据(线程复用),这也是高频生产事故。」并发题结合真实事故,立刻有说服力。
1
线程池 7 个参数
📋 new ThreadPoolExecutor 的 7 个参数
new ThreadPoolExecutor(
2, // 1. corePoolSize 核心线程数(常驻)
4, // 2. maximumPoolSize 最大线程数
60, TimeUnit.SECONDS, // 3/4. keepAliveTime + unit 空闲存活时间
new ArrayBlockingQueue<>(100), // 5. workQueue 任务队列
Executors.defaultThreadFactory(), // 6. threadFactory 线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 7. 拒绝策略
)
🧠 银行类比(必背)
线程池 = 银行网点。corePoolSize = 正常开的窗口(2 个);
workQueue = 大堂等候区(100 个座位);
maximumPoolSize = 极端时能开的最多窗口(4 个);
流程: 来任务 → 核心窗口没满开新窗口 → 满了进等候区 → 等候区满 → 开到最大窗口 → 还满 → 执行拒绝策略。
⚠️ 阿里规约:禁用 Executors
Executors.newFixedThreadPool / newSingleThreadExecutor 用 LinkedBlockingQueue(无界),任务堆积 OOM。newCachedThreadPool 最大线程 Integer.MAX_VALUE(无界),创建大量线程 OOM。
正确: 自己 new ThreadPoolExecutor,有界队列 + 明确拒绝策略。这是面试必考点,体现你不是只会调 API。
📋 4 种拒绝策略
| 策略 | 行为 | 场景 |
|---|---|---|
| AbortPolicy(默认) | 抛 RejectedExecutionException | 重要任务,要感知 |
| CallerRunsPolicy | 调用者线程自己跑 | 降级削峰(推荐) |
| DiscardPolicy | 静默丢弃 | 可丢任务(日志) |
| DiscardOldestPolicy | 丢队列最老的 | 只要最新 |
2
synchronized vs ReentrantLock
synchronized
- 关键字,JVM 层
- 自动加锁释放(不会忘)
- 锁升级:无锁→偏向→轻量→重量
- 不可中断
- 非公平锁
ReentrantLock
- API 层(AQS 实现)
- 手动 lock/ununlock(finally!)
- 可中断(lockInterruptibly)
- 可超时(tryLock)
- 可公平可非公平
- 支持多 Condition
怎么选: 简单同步用 synchronized(JVM 优化后性能不差);需要可中断/超时/公平/多条件用 ReentrantLock。synchronized 锁升级: 无竞争偏向锁(记线程 id)→ 轻微竞争自旋( CAS)→ 激烈竞争重量级锁(操作系统互斥量)。
3
volatile:可见性 + 禁止重排序
两个作用: ① 可见性:一个线程改了 volatile 变量,其他线程立即看到(强制刷主存,不用 CPU 缓存);② 禁止指令重排序(内存屏障)。不保证原子性(i++ 还是线程不安全)。
经典应用:双重检查单例
public class Singleton {
private static volatile Singleton instance; // volatile 必需!
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // new 非原子,可能重排序
}
}
}
return instance;
}
}
⚠️ 为什么 volatile 必需
instance = new Singleton() 不是原子操作,分三步:① 分配内存;② 初始化对象;③ 引用指向内存。可能重排序成 ①③②,其他线程在 ③ 后拿到引用但对象没初始化(② 没执行),用就崩。volatile 禁止重排序,保证 ② 在 ③ 前。
4
ThreadLocal:线程隔离 + 内存泄漏
⚠️ 概念澄清: ThreadLocal 和线程池是两个独立概念(不是父子关系)—— 一个做数据隔离,一个做线程复用。本节只讲 ThreadLocal 本身,想看它们详细对比(区别/场景/代码/一起用的坑),见 专题 22 · ThreadLocal vs 线程池。
🧠 原理
每个 Thread 有个 ThreadLocalMap,ThreadLocal 当 key 存值。每个线程有自己的副本,互不干扰。适合:用户上下文(当前登录用户)、数据库连接、SimpleDateFormat(非线程安全)。
⚠️ 线程池 + ThreadLocal = 内存泄漏/串数据
关键: ThreadLocalMap 的 key 是 WeakReference(ThreadLocal 弱引用),但 value 是强引用。线程池复用线程,ThreadLocal 用完不 remove,value 一直不被回收 → 内存泄漏;而且下个任务复用同线程,可能读到上个任务的值 → 串数据。
解法: 用完一定
threadLocal.remove()(finally 里)。这是高频生产事故,面试必问。
5
CAS / AQS(并发基石)
CAS(Compare And Swap): 乐观锁核心。
比较当前值==期望值?则更新为新值,原子操作(CPU 指令 cmpxchg)。失败就重试(自旋)。JUC 的 AtomicInteger 等都基于 CAS。ABA 问题: 值从 A→B→A,CAS 认为没变,用版本号解决(AtomicStampedReference)。
AQS(AbstractQueuedSynchronizer): JUC 锁的基石(ReentrantLock/Semaphore/CountDownLatch 都基于它)。核心:一个 state 变量 + 一个 CLH 等待队列。抢锁改 state,抢不到入队等待。独占模式(ReentrantLock)和共享模式(Semaphore)。
6
高频追问
1线程池参数怎么设置?核心线程数怎么算? 必问
CPU 密集型(计算):核心线程 = CPU 核数 + 1;IO 密集型(网络/DB):核心线程 = CPU 核数 × 2(或更多,因为线程多在等待)。7 参数: core/max/keepAlive/queue/factory/拒绝策略。实际生产要压测调优,别死记公式。
💬 追问「为什么 IO 密集要更多线程?」→ IO 等待时 CPU 空闲,多线程能提高 CPU 利用率。
2为什么禁止用 Executors? 高频
newFixedThreadPool/newSingleThreadExecutor 用无界队列 LinkedBlockingQueue,任务堆积 OOM;newCachedThreadPool 最大线程 Integer.MAX_VALUE,创建大量线程 OOM。自己 new ThreadPoolExecutor,有界队列 + 明确拒绝策略。这是阿里规约,也是面试高频。
3synchronized 和 Lock 区别? 必问
synchronized:JVM 层,自动释放,锁升级(偏向/轻量/重量),不可中断。ReentrantLock:API 层(AQS),手动释放(finally),可中断/超时/公平/多 Condition。简单用 synchronized,需要高级特性用 Lock。
4volatile 能保证原子性吗? 高频
不能。 volatile 只保证可见性和禁止重排序。i++ 是"读-改-写"三步,volatile 保证不了原子性,多线程 i++ 还是会丢更新。要原子用 AtomicInteger(CAS)或 synchronized。常见误区:以为 volatile 加了就线程安全。
5ThreadLocal 为什么会内存泄漏? 高频
ThreadLocalMap 的 key 是弱引用(ThreadLocal 被回收变 null),但 value 是强引用。线程池复用线程,value 不 remove 就一直不回收 → 泄漏;还可能串数据(读到上个任务的值)。解法:用完 finally 里 remove()。
6CAS 是什么?ABA 问题? 中等
CAS = 比较并交换,原子操作:当前值==期望值则更新,否则重试。是乐观锁/无锁并发核心。ABA 问题: 值 A→B→A,CAS 认为没变,可能出错(如转账)。解决:版本号(AtomicStampedReference)。JUC 原子类都基于 CAS。
7
30 秒陈述
🎤 30 秒版
「Java 并发我踩过真实坑。一是 Executors 线程池 — newFixedThreadPool 用无界队列任务堆积 OOM,改自己 new ThreadPoolExecutor 配有界队列 + CallerRunsPolicy 拒绝策略削峰。二是 ThreadLocal 在线程池里没 remove 导致串数据(线程复用)。volatile 我理解只保证可见性和禁止重排序,不保证原子性,i++ 还是要 AtomicInteger(CAS)。synchronized 锁升级(偏向→轻量→重量)我也清楚。」
面试亮点专题 15 — Java 并发与线程池
线程池 7 参数 · Executors 禁用 · synchronized vs Lock · volatile · ThreadLocal 泄漏 · CAS/AQS