为什么 Java 高并发面试题 如此重要?
在当前的互联网技术架构中,Java 高并发 是衡量后端工程师技术深度的重要标尺。无论是电商大促、秒杀系统,还是金融交易处理,都需要系统具备处理海量并发请求的能力。因此,在面试中,面试官往往会通过一系列关于 Java 高并发面试题 来考察候选人对多线程、锁机制、JVM 性能优化以及分布式系统的理解。
本页面旨在为您提供一份全面、深度且结构化的 Java 高并发面试题 备考指南。我们不仅罗列了问题,更提供了基于源码级别的分析与实战解决方案,帮助您构建完整的知识体系。
JVM 内存模型与 GC 机制
理解 JVM 是解决高并发下内存溢出(OOM)和性能抖动的前提。以下是关于 JVM 的核心考点解析。
1. 堆(Heap)与非堆(Non-Heap)
堆是 JVM 中最大的一块内存区域,主要用于存储对象实例。在高并发场景下,频繁的对象创建会导致 Young GC 频繁触发。非堆区主要存储类的元数据、常量池和静态变量。
2. 方法区(Method Area)
方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。JDK 8 之后,方法区移至元空间(Metaspace),使用本地内存。
| 内存区域 | 线程共享 | 存储内容 | 常见 OOM 异常 |
|---|---|---|---|
| 堆 | 是 | 对象实例 | java.lang.OutOfMemoryError: Java heap space |
| 虚拟机栈 | 否 | 局部变量表、操作数栈 | java.lang.StackOverflowError |
| 方法区/元空间 | 是 | 类信息、常量、静态变量 | java.lang.OutOfMemoryError: Metaspace |
1. 标记-清除(Mark-Sweep)
分为“标记”和“清除”两个阶段。标记所有需要回收的对象,统一回收。缺点是效率不高,且会产生大量不连续的内存碎片。
2. 复制(Copying)
将内存分为大小相等的两块,每次只使用其中一块。回收时将存活对象复制到另一块。优点是效率高、无碎片,缺点是内存利用率仅50%。常用于 Young 区。
3. 标记-整理(Mark-Compact)
让存活对象向一端移动,然后清理掉端边界以外的内存。解决了碎片问题,适合老年代。
// 常见 GC 日志分析示例
[GC (Allocation Failure) [PSYoungGen: 102400K->1024K(114688K)] 102400K->1032K(114688K), 0.0054321 secs]
// 含义:年轻代发生 GC,占用从 102400K 减少到 1024K,总耗时 0.005 秒
类加载过程包括:加载(Loading)→ 验证(Verification)→ 准备(Preparation)→ 解析(Resolution)→ 初始化(Initialization)→ 使用(Using)→ 卸载(Unloading)。
双亲委派模型(Parent Delegation Model)是类加载的核心机制。它确保了核心类库的安全,防止自定义类替换核心类(如 java.lang.String)。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
if (c == null) {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
}
if (c == null) {
c = findClass(name);
}
return c;
}
多线程与锁机制深度解析
线程是操作系统调度的最小单位,而锁是保证线程安全的关键。在 Java 高并发面试题 中,这部分内容占据了极大比重。
1. 实现层面:synchronized 是 JVM 内置关键字,基于 Monitor 对象实现;ReentrantLock 是 JDK 层面的 API,基于 AQS(AbstractQueuedSynchronizer)实现。
2. 功能丰富度:ReentrantLock 提供更灵活的功能,如尝试获取锁(tryLock)、超时获取锁、公平锁/非公平锁选择、多个 Condition 对象。
3. 性能:在 JDK 1.6 之前,synchronized 性能较差;JDK 1.6 引入了偏向锁、轻量级锁等优化后,两者性能差距缩小,但在高竞争下 ReentrantLock 通常更具优势。
CAS(Compare-And-Swap)是一种无锁算法。它包含三个操作数:内存值 V、旧的预期值 E、要修改的新值 N。当且仅当预期值 E 和内存值 V 相同时,将内存值 V 修改为 N,否则什么都不做。
缺点:
- ABA 问题:值从 A 变成 B 又变回 A,CAS 无法察觉。
- 循环时间长开销大:如果 CAS 一直不成功,会一直自旋,消耗 CPU。
- 只能保证一个共享变量的原子操作。
AQS(AbstractQueuedSynchronizer)是构建锁和其他同步组件的基础框架。它使用一个 int 类型的 state 变量来表示同步状态。
1. 获取资源:线程尝试修改 state,如果成功则获取资源,否则进入 CLH 队列等待。
2. 释放资源:线程修改 state,唤醒队列中的下一个节点。
ReentrantLock、Semaphore、CountDownLatch 等都是基于 AQS 实现的。
线程池实战与配置策略
线程池是 Java 高并发 编程中的核心组件,用于管理线程生命周期并复用线程。面试中常考 ThreadPoolExecutor 的参数及其拒绝策略。
核心参数解析
- corePoolSize: 核心线程数,即使空闲也会保留。
- maximumPoolSize: 最大线程数,包括核心线程和非核心线程。
- keepAliveTime: 非核心线程空闲时的存活时间。
- workQueue: 任务队列,如 ArrayBlockingQueue, LinkedBlockingQueue。
- threadFactory: 线程工厂,用于创建新线程。
- handler: 拒绝策略,如 AbortPolicy, CallerRunsPolicy。
常见线程池类型
- NewFixedThreadPool: 固定大小线程池,适合负载较重的服务器。
- NewCachedThreadPool: 可缓存线程池,适合 IO 密集型任务。
- NewSingleThreadExecutor: 单线程池,保证任务顺序执行。
- NewScheduledThreadPool: 支持定时及周期性任务执行。
自定义线程池建议
阿里巴巴开发手册强制规定:线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。这样可以明确线程池的运行规则,规避资源耗尽的风险。
线程池配置示例
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // corePoolSize
10, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), // workQueue
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy() // handler
);
分布式锁与 Redis 高可用
在微服务和分布式架构下,单机锁(synchronized/ReentrantLock)已无法满足需求。分布式锁成为 Java 高并发面试题 中的高阶考点。
使用 SET key value NX PX milliseconds 命令实现。优点是实现简单,性能高。缺点是如果主从切换导致锁丢失(主节点宕机未同步从节点),需要 Redisson 等框架实现看门狗机制保证锁的原子性和续期。
利用 ZK 的临时顺序节点特性。优点是实现强一致性,锁释放可靠(会话断开自动删除)。缺点是性能相对 Redis 较低,因为每次加锁解锁都涉及 ZK 的写操作,适合对一致性要求极高的场景。
利用数据库的唯一索引约束。缺点是高并发下数据库压力大,且存在锁等待超时问题,通常不推荐用于高并发核心链路。
高并发场景实战演练
理论结合实际,以下是几个典型的高并发场景及其解决方案,这些也是 Java 高并发面试题 中经常出现的开放性问题。
1. 秒杀系统架构设计
痛点: 瞬间流量巨大,数据库连接池耗尽,服务宕机。
解决方案:
- ⚡ 静态资源隔离: 将页面静态化,部署在 CDN 或 Nginx 上,减少后端压力。
- ⚡ 接口限流: 使用 Sentinel 或 Guava RateLimiter 限制每秒请求数。
- ⚡ 缓存预热: 将商品信息提前加载到 Redis,减少 DB 读取。
- ⚡ 异步处理: 使用 RabbitMQ 或 Kafka 削峰填谷,用户下单后发送消息,后端异步扣库存。
- ⚡ 库存扣减: 使用 Lua 脚本在 Redis 中原子扣减库存,防止超卖。
2. 缓存穿透、击穿与雪崩
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 缓存穿透 | 查询不存在的数据,请求直达 DB | 布隆过滤器、缓存空对象 |
| 缓存击穿 | 热点 Key 过期,大量请求瞬间打到 DB | 设置热点数据永不过期、互斥锁 |
| 缓存雪崩 | 大量 Key 同时过期,或 Redis 宕机 | Key 过期时间加随机值、Redis 集群高可用 |