# 八股-Java并发
这份清单非常全面,涵盖了 Java 并发编程从底层原理到应用实践的核心面试题。为了让你能够快速复习并理解核心概念,我将其整理为分类清晰的 Markdown 表格。
我将这些问题按**知识领域**(AQS、锁机制、线程池、JMM、虚拟线程等)进行了归类,以便于体系化记忆。
---
### 1. AQS (AbstractQueuedSynchronizer) 与 Lock 体系
|**问题**|**技术性回答 (关键词)**|**小白都能看懂的解释**|
|---|---|---|
|**如何理解 AQS?**|**构建锁的框架**,核心是 `state` (资源状态) + **CLH 双向队列** (线程等待容器)。通过模板方法模式定义获取/释放资源的逻辑。|AQS 是盖房子的地基和图纸。你想造什么锁(独占锁、共享锁),按照它的规范填空就行,不用从头挖地基。|
|**AQS 为什么采用双向链表?**|1. **取消节点**:需要访问前驱节点将其移除。<br><br> <br><br>2. **唤醒后继**:当前节点释放锁后,需唤醒后继节点。双向便于双向遍历和操作。|排队时,你不仅要知道前面是谁(万一他走了,你得补位),还要知道后面是谁(你办完事了,得拍拍他叫他去办理)。|
|**AQS 如何实现等待和唤醒?**|**LockSupport.park()** 挂起线程,**LockSupport.unpark()** 唤醒线程。配合队列节点的自旋 (`acquireQueued`) 判断。|就像交警(LockSupport):吹哨让你停下(park),或者挥手让你走(unpark)。|
|**AQS 同步队列和条件队列原理?**|**同步队列**:双向链表,争抢锁失败的线程。<br><br> <br><br>**条件队列**:单向链表,调用 `await()` 的线程。`signal()` 时节点从条件队列移至同步队列。|**同步队列**是银行柜台前排队的队伍;**条件队列**是没带身份证被叫去休息区等待的人。拿到身份证(signal)后,从休息区重新回到柜台队伍排队。|
|**什么是 AQS 的独占和共享模式?**|**独占**:`tryAcquire`,一次只允许一个线程(如 ReentrantLock)。<br><br> <br><br>**共享**:`tryAcquireShared`,允许多个线程同时持有(如 Semaphore, CountDownLatch)。|**独占**:这厕所由于只有一个坑位,只能进一个人。<br><br> <br><br>**共享**:这公园有5个门,可以同时进5个人。|
|**可重入锁及其实现?**|**ReentrantLock**。通过 AQS 的 `state` 计数。持有锁的线程再次获取时,`state + 1`;释放时 `-1`,为 0 时彻底释放。|你进了大门(拿锁),进卧室(又拿锁),进厕所(又拿锁)。只要是你,不用重新排队,只是出门时要退三步才能彻底离开。|
|**公平锁与非公平锁区别?**|**公平**:严格按队列顺序拿锁(先来后到)。<br><br> <br><br>**非公平**:新来的线程先尝试抢一次(CAS),抢不到再排队。性能通常非公平更高。|**公平**:老实排队。<br><br> <br><br>**非公平**:新来的人先试着插个队,插不进去再老实排队。|
|**CountDownLatch, CyclicBarrier, Semaphore 区别?**|**Latch**:做减法,一等多(不可重用)。<br><br> <br><br>**Barrier**:做加法,多等一(可重用)。<br><br> <br><br>**Semaphore**:控制并发数(限流)。|**Latch**:监考老师等所有学生交卷才能走。<br><br> <br><br>**Barrier**:导游举旗,所有人到齐了才能出发去下一站。<br><br> <br><br>**Semaphore**:停车场只有5个车位,出来一辆才能进一辆。|
### 2. Synchronized 与 原生锁机制
|**问题**|**技术性回答 (关键词)**|**小白都能看懂的解释**|
|---|---|---|
|**Synchronized 怎么实现的?**|**Monitor (监视器锁)**。字节码层面:`monitorenter`/`monitorexit`。底层依赖操作系统的 Mutex Lock。|给对象贴封条。进入代码块撕封条,出去贴封条。|
|**Synchronized 锁的是什么?**|1. 普通方法:锁 **当前实例对象 (this)**。<br><br> <br><br>2. 静态方法:锁 **Class 对象**。<br><br> <br><br>3. 代码块:锁 **指定对象**。|锁的是具体的“房产证”。普通方法锁这间房,静态方法锁整栋楼的图纸。|
|**Synchronized 锁升级过程?**|**无锁** -> **偏向锁** (记录线程ID) -> **轻量级锁** (自旋 CAS) -> **重量级锁** (OS 互斥量,阻塞)。|**偏向**:门上贴个便利贴“我占了”。<br><br> <br><br>**轻量**:有人来抢,大家在门口转圈圈猜拳(不睡觉)。<br><br> <br><br>**重量**:人太多了,叫保安来,输的人全部去睡觉等待叫号。|
|**Synchronized 是公平锁吗?**|**非公平**。重量级锁唤醒策略不保证顺序;轻量级锁升级也是竞争。|不公平。不管你排了多久,只要门开了,刚好路过的人可能直接溜进去。|
|**Synchronized 怎么保证原子/可见/有序性?**|**原子性**:互斥执行。<br><br> <br><br>**可见性**:解锁前刷新主存,加锁时读主存。<br><br> <br><br>**有序性**:单线程内看起来是有序的(As-if-serial)。|因为同一时间只有一个人在屋里干活,别人看不到过程,只看到结果,所以看起来一切都完美。|
|**为什么 JDK 15 废弃偏向锁?**|维护成本高,现代应用并发度高,偏向锁撤销会有 Stop The World (STW) 开销,收益不如直接由轻量级锁接管。|以前觉得“一人独占”的情况多,现在大家都很忙,总是各种人来抢,维护“专属VIP”的成本比直接竞争还高。|
|**Synchronized 和 ReentrantLock 区别?**|**Sync**:JVM 层面,自动加解锁,不可中断,非公平。<br><br> <br><br>**Lock**:API 层面,手动加解锁,可中断,支持公平/非公平,支持 Condition。|**Sync**:傻瓜相机,按快门就行。<br><br> <br><br>**Lock**:单反相机,能调光圈快门(功能多),但忘了盖镜头盖(忘记 unlock)就完了。|
### 3. CAS (Compare And Swap) 与 原子类
|**问题**|**技术性回答 (关键词)**|**小白都能看懂的解释**|
|---|---|---|
|**什么是 CAS?存在什么问题?**|**乐观锁**。比较预期值与内存值,一致则更新。CPU指令 `cmpxchg`。<br><br> <br><br>**问题**:ABA 问题、循环时间长开销大、只能保证一个变量原子性。|修改前先看一眼:“还是旧值吗?”是就改,不是就重试。<br><br> <br><br>**问题**:你女朋友离开了一会儿又回来了,你以为她没离开过(ABA)。|
|**CAS 一定有自旋吗?**|**不一定**。CAS 是单次原子操作。**自旋**是上层(如 AtomicInteger)为了保证成功而编写的 `do-while` 循环。|这里的“自旋”就是“死皮赖脸”。CAS 只是“试一次”,CAS + 循环 才是“试到成功为止”。|
|**CAS 在 OS 层面如何保证原子性?**|多核 CPU 下使用 **Lock 指令前缀**,锁定北桥信号或缓存行 (Cache Locking),配合 MESI 协议。|CPU 也就是在总线上吼一声:“这条数据我占了,谁也别动!”|
|**LongAdder 和 AtomicLong 区别?**|**AtomicLong**:单点热点竞争,高并发性能下降。<br><br> <br><br>**LongAdder**:**分散热点** (Cell 数组),最后汇总。空间换时间。|**AtomicLong**:所有人挤一个窗口存钱。<br><br> <br><br>**LongAdder**:开了10个窗口,大家随便存,最后行长把10个窗口的钱加起来。|
|**如何保证多线程下 i++ 正确?**|使用 `AtomicInteger` (CAS) 或 `synchronized`/`Lock` 加锁。|`i++` 分三步(读、改、写),不加锁或不用原子类,就像三个人同时往同一个格子里填数字,会互相覆盖。|
|**ABA 问题及解决?**|值从 A -> B -> A。解决:**版本号** (`AtomicStampedReference`)。|给数据加个时间戳或版本号。不仅看值对不对,还要看版本变没变。|
### 4. 线程池与执行器 (Executor)
|**问题**|**技术性回答 (关键词)**|**小白都能看懂的解释**|
|---|---|---|
|**ForkJoinPool 和 ThreadPoolExecutor 区别?**|**FJP**:工作窃取算法 (Work-Stealing),适合 CPU 密集型、递归分治任务。<br><br> <br><br>**TPE**:核心线程+队列,适合 IO 密集或普通任务。|**TPE**:包工头分任务,一人一个。<br><br> <br><br>**FJP**:也是分任务,但如果你干完了,可以去偷隔壁没干完的活来干(不仅勤劳还聪明)。|
|**为什么不建议用 Executors 构建线程池?**|默认的 `CachedThreadPool` 允许最大线程数 `Integer.MAX_VALUE` (OOM);`FixedThreadPool` 允许队列无限长 (OOM)。|官方给的“快捷方式”是个坑,要么招无限多的人把公司吃垮,要么接无限多的单把仓库撑爆。|
|**线程池的拒绝策略有哪些?**|AbortPolicy (抛异常), CallerRunsPolicy (主线程自己干), DiscardPolicy (丢弃), DiscardOldestPolicy (丢弃最老的)。|1. 报警!<br><br> <br><br>2. 谁喊我干活谁自己干!<br><br> <br><br>3. 装作没听见。<br><br> <br><br>4. 把排队最早的踢了,让你进。|
|**如何让线程池顺序执行任务?**|只能用 **单线程线程池** (`newSingleThreadExecutor`) 或者在任务内部通过 `CompletableFuture` 编排。|想排队只有一种办法:只开一个窗口。|
|**线程数设定成多少合适?**|**CPU 密集型**:N+1。<br><br> <br><br>**IO 密集型**:2N 或 N / (1 - 阻塞系数)。|**算术题多**:CPU核数够用就行,人多也没笔。<br><br> <br><br>**搬砖运货多**:多招点人,因为大部分时间都在路上(IO等待),不用动脑子。|
|**CompletableFuture 底层实现?**|基于 **观察者模式** + **DriverStack** (栈结构存储依赖任务) + **CAS** 控制状态 + **ForkJoinPool** 异步执行。|任务链条。就像多米诺骨牌,这一块倒了(完成),自动触发下一块。|
### 5. JMM (Java 内存模型) 与 关键字
|**问题**|**技术性回答 (关键词)**|**小白都能看懂的解释**|
|---|---|---|
|**什么是 JMM?**|抽象模型,屏蔽硬件差异。核心:**主内存** vs **工作内存**。规定了原子性、可见性、有序性。|公司规章制度。规定了员工(线程)如何在自己的办公桌(工作内存)和档案室(主内存)之间存取文件。|
|**Volatile 保证可见性和有序性?**|**可见性**:写后立即刷回主存,读时强制从主存读。<br><br> <br><br>**有序性**:禁止指令重排序 (**内存屏障** / Lock 前缀)。|**可见性**:改了文件立刻广播通告。<br><br> <br><br>**有序性**:禁止自作聪明调整工作顺序。|
|**Volatile 能保证原子性吗?**|**不能**。它不保证复合操作(如 `i++`)的原子性。|它只能保证你看到的是最新的,但不能保证你改的时候别人没插手。|
|**有了 CAS/Sync 为什么还要 Volatile?**|Volatile 是**轻量级**的同步机制,无上下文切换,适合主要用于状态标记(flag)。|杀鸡焉用牛刀。只是想看个信号灯,没必要把路封了(Sync)。|
|**Happens-before 是什么?**|JMM 核心概念。操作 A 的结果对操作 B 可见。涵盖:程序顺序、锁、Volatile、线程启动/终止等规则。|“先发生原则”。如果 A 必须在 B 之前发生,那么 A 做的事,B 必须能看见。|
|**到底什么是内存屏障?**|CPU 指令(LoadLoad, StoreStore 等)。防止指令重排序,保证特定操作的顺序。|路障。告诉 CPU:这个路障上面的代码没执行完,绝对不能执行路障下面的代码。|
|**int a = 1 是原子操作吗?**|**是**。基本数据类型赋值(32位系统下的 long/double 除外)是原子的。|是。你要么写了个1,要么没写,不会写半个1。|
### 6. 线程基础与生命周期
| **问题** | **技术性回答 (关键词)** | **小白都能看懂的解释** |
| ----------------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| **Java 线程状态及流转?** | New -> Runnable (Ready/Running) -> Blocked / Waiting / Timed_Waiting -> Terminated。 | 新建 -> 排队/干活 -> 堵车/休息/定时休息 -> 离职。 |
| **Thread.sleep(0) 的作用?** | 触发 **Safepoint** (安全点),让出 CPU 时间片,让其他线程有机会执行(触发 GC 或系统调度)。 | 假装休息一下,其实是为了让系统检查一下状态(比如是不是该倒垃圾了),或者给别人让个路。 |
| **Java 如何判断线程是否存活?** | 检查线程状态 (`isAlive()`),或者看它是否还在 ThreadGroup 中。底层映射到 OS 线程。 | 还有气儿吗?(Thread 对象关联的 OS 线程句柄还在不在)。 |
| **子线程异常,进程为何不退出?** | 线程是独立的执行单元。只有所有**非守护线程**结束,JVM 才会退出。 | 作为一个团队,一个员工(子线程)疯了被保安拖走了,公司(进程)还得继续转,除非老板(Main线程)也走了。 |
| **什么是守护线程?** | `setDaemon(true)`。为用户线程服务(如 GC 线程)。所有用户线程结束,守护线程自动死掉。 | 公司的保洁阿姨。员工都下班了,保洁阿姨也就下班了,不需要专门等她。 |
| **父子线程怎么共享数据?** | `InheritableThreadLocal`。创建子线程时复制父线程 Map 中的值。 | 父亲给儿子传家宝。 |
| **为什么不能在 try-catch 捕获子线程异常?** | 异常是线程私有的栈空间内抛出,无法跨线程传播到主线程栈。 | 你在二楼喊破喉咙,一楼的人戴着耳机(不同的调用栈)是听不见的。 |
| **如何实现主线程捕获子线程异常?** | 1. `Thread.setUncaughtExceptionHandler`。<br><br> <br><br>2. 使用 `Future.get()` 捕获 `ExecutionException`。 | 1. 给子线程装个报警器。<br><br> <br><br>2. 用 Future 这种对讲机,子线程出事了通过对讲机告诉你。 |
### 7. 虚拟线程 (Virtual Threads - JDK 21+)
| **问题** | **技术性回答 (关键词)** | **小白都能看懂的解释** |
| ---------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------- |
| **JDK21 虚拟线程是怎么回事?** | **用户态线程** (M:N 模型)。由 JVM 调度,挂载在 Carrier Thread (平台线程) 上。IO 阻塞时自动卸载 (`Continuation`)。 | 以前是一个工人负责一个任务(太贵)。现在是一个工人同时看着几百个任务,谁卡住了就把它放一边,先做别的(极低成本)。 |
| [[为什么虚拟线程不能用 Synchronized?]] | **Pinning (钉住)** 问题。在 Sync 块中发生 IO,虚拟线程无法从载体线程卸载,导致死锁或性能退化。建议用 `ReentrantLock`。 | `Synchronized` 是强力胶,把轻量级任务和重体力工人粘在了一起,任务停了,工人也走不了。 |
| **为什么虚拟线程避免用 ThreadLocal?** | 虚拟线程数量极大(百万级),每个都存 ThreadLocal 会导致 **OOM (内存溢出)**。 | 以前只有几个人,每人配个柜子(ThreadLocal)没事。现在有几百万个临时工,每人配个柜子,仓库直接炸了。 |
| **ForkJoinPool 和虚拟线程的关系?** | 虚拟线程的默认调度器就是 `ForkJoinPool`。 | 虚拟线程是“任务”,ForkJoinPool 是调度这些任务的“总指挥部”。 |
### 8. ThreadLocal 与 内存泄漏
|**问题**|**技术性回答 (关键词)**|**小白都能看懂的解释**|
|---|---|---|
|**ThreadLocal 为什么内存泄漏?**|Map 的 Key 是 **弱引用**,Value 是 **强引用**。Key 被 GC 后为 null,但 Value 还在,且无法访问,直到线程结束。|存包柜的钥匙(Key)丢了(GC了),但柜子里的包(Value)还在占地方,而且你再也打不开了。|
|**如何解决内存泄漏?**|使用完必须调用 `remove()` 方法。|走的时候一定要把柜子清空。|
|**ThreadLocal 应用场景?**|1. 线程隔离(每个线程独立的 SimpleDateFormat)。<br><br> <br><br>2. 上下文传递(UserContext, Cookie, Session)。|1. 每个人发一支笔,不用抢。<br><br> <br><br>2. 也就是“当前登录用户”这种随身带着的信息。|
|**Inheritable vs Transmittable (TTL)?**|**ITL**:仅支持父子线程创建时传递。<br><br> <br><br>**TTL** (阿里开源):支持**线程池**中复用线程时的上下文传递 (通过修饰 Runnable)。|**ITL**:爸爸给亲生儿子传家宝。<br><br> <br><br>**TTL**:不管你是亲儿子还是借来的(线程池复用),只要是你干活,就把装备给你带上。|
### 9. 杂项与综合
| **问题** | **技术性回答 (关键词)** | **小白都能看懂的解释** |
| ----------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| **什么是 Unsafe?** | JDK 内部后门类。提供直接操作内存、CAS、线程挂起等底层能力。不建议应用层直接用。 | 也就是 Java 的“黑魔法”。能直接操作内存,威力大但容易走火入魔(崩溃)。 |
| **什么是上下文切换?** | CPU 从一个线程切换到另一个线程。保存当前上下文(寄存器、PC),加载新上下文。耗时。 | 甚至当你正在写作业,妈妈叫你去洗碗。你得记住写到哪了,换个脑子去洗碗,回来还得想刚才写到哪。这个过程很累。 |
| **三个线程 T1,T2,T3 顺序执行?** | 1. `T1.join(); T2.join()`。<br><br> <br><br>2. `SingleThreadExecutor`。<br><br> <br><br>3. `CountDownLatch` 递减。 | 接力赛。T1 跑完把棒子给 T2,T2 跑完给 T3。 |
| [[三个线程顺序打印 0-100?]] | 状态机 + `wait/notify` 或 `Lock/Condition`。取模判断 `i % 3 == 0/1/2`。 | 喊号。A喊完唤醒B,B喊完唤醒C,C喊完唤醒A。 |
| [[并发与并行的区别?]] | **并发 (Concurrent)**:单位时间应对多件事(交替执行)。<br><br> <br><br>**并行 (Parallel)**:同一时刻做多件事(多核同时)。 | **并发**:一个厨师,一会儿切菜一会儿炒菜。<br><br> <br><br>**并行**:两个厨师,一个切菜,一个炒菜。 |
| [[死锁及解决?]] | **条件**:互斥、占有且等待、不可抢占、循环等待。<br><br> <br><br>**解决**:破坏循环等待(按顺序加锁),使用 `tryLock`(超时放弃)。 | **死锁**:哲学家就餐问题。左手拿一支筷子,右手等别人的筷子。<br><br> <br><br>**解决**:规定必须先拿号码小的筷子,或者拿不到就放下。 |
---
### 需要我为你做些什么?
你可以任选一个方向让我深入:
1. **代码实现**:比如“写一个三个线程交替打印 ABC 的 Demo”。
2. **深度解析**:比如“详细画图解释 AQS 的入队和出队流程”。
3. **场景设计**:比如“设计一个高并发的秒杀系统,如何利用上述知识点”。
## 面试题
- AQS为什么采用双向链表?
- AQS是如何实现线程的等待和唤醒的?
- AQS的同步队列和条件队列原理?
- CAS一定有自旋吗?
- CAS在操作系统层面是如何保证原子性的?
- CompletableFuture的底层是如何实现的?
- CountDownLatch、CyclicBarrier、Semaphore区别?
- ForkJoinPool和ThreadPoolExecutor区别是什么?
- JDK21 中的虚拟线程是怎么回事?
- Java是如何判断一个线程是否存活的?
- Java线程出现异常,进程为啥不会退出?
- LongAdder和AtomicLong的区别?
- Thread.sleep(0)的作用是什么?
- ThreadLocal为什么会导致内存泄漏?如何解决的?
- ThreadLocal的应用场景有哪些?
- happens-before和as-if-serial有啥区别和联系?
- int a = 1 是原子性操作吗
- run_start、wait_sleep、notify_notifyAll区别_
- sychronized是非公平锁吗,那么是如何体现的?
- synchronized 的锁能降级吗?
- synchronized和reentrantLock区别?
- synchronized是如何保证原子性、可见性、有序性的?
- synchronized是怎么实现的?
- synchronized的重量级锁很慢,为什么还需要重量级锁?
- synchronized的锁优化是怎样的?
- synchronized的锁升级过程是怎样的?
- synchronized锁的是什么?
- volatile是如何保证可见性和有序性的?
- volatile能保证原子性吗?为什么?
- 三个线程分别顺序打印0-100
- 为什么JDK 15要废弃偏向锁?
- 为什么不建议通过Executors构建线程池
- 为什么不能在try-catch中捕获子线程的异常_
- 为什么虚拟线程不能用synchronized?
- 为什么虚拟线程不要和线程池一起用?
- 为什么虚拟线程尽量避免使用ThreadLocal
- 什么是AQS的独占模式和共享模式?
- 什么是CAS?存在什么问题?
- 什么是Java内存模型(JMM)?
- 什么是ThreadLocal,如何实现的?
- 什么是Unsafe?
- 什么是happens-before原则?
- 什么是可重入锁,怎么实现可重入锁?
- 什么是多线程中的上下文切换?
- 什么是守护线程,和普通线程有什么区别?
- 什么是并发,什么是并行?
- 什么是总线嗅探和总线风暴,和JMM有什么关系?
- 什么是死锁,如何解决?
- 什么是线程池,如何实现的?
- 公平锁和非公平锁的区别?
- 创建线程有几种方式?
- 到底啥是内存屏障?到底怎么加的?
- 如何保证多线程下 i++ 结果正确?
- 如何实现主线程捕获子线程异常
- 如何对多线程进行编排
- 如何理解AQS?
- 如何让Java的线程池顺序执行任务?
- 并发编程中的原子性和数据库ACID的原子性一样吗?
- 有三个线程T1,T2,T3如何保证顺序执行?
- 有了CAS为啥还需要volatile?
- 有了InheritableThreadLocal为啥还需要TransmittableThreadLocal?
- 有了MESI为啥还需要JMM?
- 有了synchronized为什么还需要volatile_
- 有哪些实现线程安全的方案_
- 父子线程之间怎么共享_传递数据?
- 线程同步的方式有哪些?
- 线程数设定成多少更合适?
- 线程是如何被调度的?
- 线程有几种状态,状态之间的流转是怎样的?
- 线程池的拒绝策略有哪些?
- 能不能谈谈你对线程安全的理解?