🎬 怎么引出
「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

← 返回首页