为什么 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 高并发面试题 中,这部分内容占据了极大比重。

synchronized 与 ReentrantLock 的区别是什么?

1. 实现层面:synchronized 是 JVM 内置关键字,基于 Monitor 对象实现;ReentrantLock 是 JDK 层面的 API,基于 AQS(AbstractQueuedSynchronizer)实现。

2. 功能丰富度:ReentrantLock 提供更灵活的功能,如尝试获取锁(tryLock)、超时获取锁、公平锁/非公平锁选择、多个 Condition 对象。

3. 性能:在 JDK 1.6 之前,synchronized 性能较差;JDK 1.6 引入了偏向锁、轻量级锁等优化后,两者性能差距缩小,但在高竞争下 ReentrantLock 通常更具优势。

什么是 CAS 机制?它有什么缺点?

CAS(Compare-And-Swap)是一种无锁算法。它包含三个操作数:内存值 V、旧的预期值 E、要修改的新值 N。当且仅当预期值 E 和内存值 V 相同时,将内存值 V 修改为 N,否则什么都不做。

缺点:

  • ABA 问题:值从 A 变成 B 又变回 A,CAS 无法察觉。
  • 循环时间长开销大:如果 CAS 一直不成功,会一直自旋,消耗 CPU。
  • 只能保证一个共享变量的原子操作。
AQS 的原理是什么?

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 高并发面试题 中的高阶考点。

方案一:基于 Redis (SETNX)

使用 SET key value NX PX milliseconds 命令实现。优点是实现简单,性能高。缺点是如果主从切换导致锁丢失(主节点宕机未同步从节点),需要 Redisson 等框架实现看门狗机制保证锁的原子性和续期。

方案二:基于 Zookeeper (ZNode)

利用 ZK 的临时顺序节点特性。优点是实现强一致性,锁释放可靠(会话断开自动删除)。缺点是性能相对 Redis 较低,因为每次加锁解锁都涉及 ZK 的写操作,适合对一致性要求极高的场景。

方案三:基于数据库 (Unique Key)

利用数据库的唯一索引约束。缺点是高并发下数据库压力大,且存在锁等待超时问题,通常不推荐用于高并发核心链路。

高并发场景实战演练

理论结合实际,以下是几个典型的高并发场景及其解决方案,这些也是 Java 高并发面试题 中经常出现的开放性问题。

1. 秒杀系统架构设计

痛点: 瞬间流量巨大,数据库连接池耗尽,服务宕机。

解决方案:

  • ⚡ 静态资源隔离: 将页面静态化,部署在 CDN 或 Nginx 上,减少后端压力。
  • ⚡ 接口限流: 使用 Sentinel 或 Guava RateLimiter 限制每秒请求数。
  • ⚡ 缓存预热: 将商品信息提前加载到 Redis,减少 DB 读取。
  • ⚡ 异步处理: 使用 RabbitMQ 或 Kafka 削峰填谷,用户下单后发送消息,后端异步扣库存。
  • ⚡ 库存扣减: 使用 Lua 脚本在 Redis 中原子扣减库存,防止超卖。

2. 缓存穿透、击穿与雪崩

现象 原因 解决方案
缓存穿透 查询不存在的数据,请求直达 DB 布隆过滤器、缓存空对象
缓存击穿 热点 Key 过期,大量请求瞬间打到 DB 设置热点数据永不过期、互斥锁
缓存雪崩 大量 Key 同时过期,或 Redis 宕机 Key 过期时间加随机值、Redis 集群高可用