Skip to content

Java 只会基本语法,如何准备面试

适用人群:会变量、流程控制、数组、方法、基础面向对象,但还没有系统学习 Java 后端的人。

目标:先拿到一份初级 Java 开发 / Java 后端实习岗位,再逐步补齐工程能力。

五、高频问题清单

Java 基础

1. ArrayList 和 LinkedList 怎么选?

ArrayList 基于动态数组,按下标随机访问快,尾部追加通常快,适合读多写少的场景。LinkedList 基于链表,按下标查找慢,在已经定位节点的情况下插入和删除方便。实际开发中优先使用 ArrayList,只有明确存在大量中间插入删除且能快速定位节点时才考虑 LinkedList。

2. HashMap 为什么需要 hashCode()?

HashMap 先通过 key 的 hashCode 定位桶,再使用 equals 比较桶内的 key 是否相同。hashCode 相同不代表对象一定相同,只能说明可能发生哈希冲突;equals 相等的对象必须具有相同的 hashCode。自定义对象作为 key 时,通常要同时重写 equals 和 hashCode。

3. String 为什么设计成不可变?

String 的值创建后不能改变。这样可以保证字符串常量池、哈希值缓存和多线程共享的安全性,也适合用作 HashMap 的 key。字符串拼接较多时,应使用 StringBuilder,避免频繁创建新的 String 对象。

4. finally 一定会执行吗?

正常情况下,无论 try 或 catch 是否抛出异常,finally 都会执行。但以下情况可能不执行:进程被强制终止、虚拟机退出、线程或系统突然崩溃,以及在 finally 之前执行了 System.exit。不要在 finally 中随意 return,因为它可能覆盖 try 或 catch 中的返回值和异常。

5. 重载和重写有什么区别?

重载发生在同一个类中,方法名相同但参数列表不同,属于编译期多态,返回值不能单独作为重载条件。重写发生在子类和父类之间,子类提供相同方法签名的新实现,属于运行期多态,不能降低访问权限,也不能抛出更宽泛的受检异常。

6. 接口中的默认方法是什么?

接口默认方法使用 default 修饰,可以带方法实现。它允许接口在增加新方法时尽量保持已有实现类兼容。实现类可以直接继承,也可以重写默认方法;如果多个接口存在同名默认方法,实现类必须明确重写解决冲突。

数据库

7. 什么情况下索引会失效?

常见情况包括:对索引列使用函数或表达式、隐式类型转换、以通配符开头的 LIKE、违反联合索引最左匹配原则、使用不等于或范围条件导致选择性很低,以及对大量数据进行排序或计算。最终要通过 EXPLAIN 查看执行计划,不能只凭经验判断。

8. WHERE 和 HAVING 的区别?

WHERE 在分组前过滤行,不能直接用于聚合结果;HAVING 在 GROUP BY 分组后过滤分组,通常用于 COUNT、SUM 等聚合条件。能在 WHERE 过滤的条件尽量提前过滤,以减少参与分组的数据量。

9. 事务的四个特性是什么?

ACID:原子性(事务中的操作要么全部成功,要么全部回滚)、一致性(事务前后数据满足约束)、隔离性(并发事务相互隔离)、持久性(提交后的数据不会因普通故障丢失)。

10. 如何排查一条 SQL 很慢?

先确认慢在数据库还是应用层,再用 EXPLAIN 查看是否走索引、扫描行数和连接方式;检查慢查询日志、SQL 条件、排序、分页、JOIN 和返回数据量;最后结合表结构、索引和实际数据分布优化,并通过压测验证。

Spring

11. 什么是 IOC 和 AOP?

IOC 是控制反转,把对象的创建、组装和生命周期管理交给 Spring 容器;DI 是 IOC 的具体实现方式,即依赖注入。AOP 是面向切面编程,把事务、日志、权限等横切逻辑从业务代码中抽离,通过代理在方法执行前后织入。

12. Autowired 的基本原理是什么?

Spring 启动时扫描组件并创建 Bean,AutowiredAnnotationBeanPostProcessor 会解析 Autowired 注解,根据类型查找依赖并注入;如果同类型 Bean 有多个,需要使用 Qualifier 或 Primary 指定。实际项目中优先使用构造器注入,依赖关系明确且便于测试。

13. Transactional 为什么可能不生效?

常见原因有:同类内部调用绕过 Spring 代理、方法不是 public、异常被 catch 后没有抛出、异常类型不符合回滚规则、调用发生在异步线程、数据库引擎不支持事务,以及事务配置或数据源配置错误。排查时先确认调用是否经过代理,再检查异常、数据源和数据库配置。

14. Controller、Service、DAO 分别负责什么?

Controller 负责接收和校验请求、组织响应;Service 负责业务规则、事务边界和多个数据操作的编排;DAO 或 Mapper 负责数据库访问。分层可以降低耦合,但不要为了分层把简单逻辑重复复制到多个类中。

场景题

15. 接口返回 500,你如何排查?

先查看请求时间、请求参数、TraceId 和服务日志,定位异常堆栈最底层 cause;再检查 Controller、Service、Mapper 调用链、数据库连接和 SQL;如果涉及远程服务,检查超时和响应;最后使用断点、复现请求或增加结构化日志验证修复结果。不能只根据 500 状态码猜原因。

16. 用户重复点击提交按钮怎么办?

前端可以禁用按钮并展示处理中状态,但不能只依赖前端。后端应使用业务唯一号或幂等 Token,结合数据库唯一约束、状态条件更新或 Redis 分布式锁;支付、下单等关键操作还要设计状态机和重试机制,保证重复请求不会重复扣款或创建业务数据。

17. 如何设计一个简单的登录功能?

用户注册时对密码加盐哈希后保存,登录时校验账号和密码,成功后签发 Session 或 Token;后续请求由 Filter 或 Interceptor 校验凭证并加载用户身份;设置过期时间、退出登录和失败次数限制。密码不能明文保存,登录接口还应考虑 HTTPS、验证码、限流和审计日志。

18. 缓存和数据库数据不一致怎么办?

常见策略是先更新数据库,再删除缓存;读取时缓存不存在再查询数据库并回填。对高并发或重要数据,可以使用延迟双删、消息队列重试、版本号或定时校正。缓存只作为加速层,最终一致性方案要结合业务容忍度设计。

不会的问题不要硬编。可以这样回答:

这个问题我目前没有深入实践过,但我知道它主要涉及_。如果需要排查,我会先检查_,再通过____验证。面试后我会补齐这部分。

七、面试前最后检查

  • 能独立创建并运行一个 Spring Boot 项目。
  • 能写常见增删改查和分页 SQL。
  • 能解释集合、异常、接口、事务等基础概念。
  • 能现场写出简单算法,并说明复杂度。
  • 能完整介绍一个项目,不夸大自己的职责。
  • 能使用 Git、日志和断点进行基本排查。
  • 简历上的每一项技术都准备一个真实例子。

九、Spring Boot 高频面试题

下面的问题建议按“概念 + 使用场景 + 项目例子”的方式准备,不要只背一句定义。

1. Spring Boot 解决了什么问题?

Spring Boot 通过自动配置、起步依赖和内嵌服务器,减少传统 Spring 项目的 XML 配置,让项目可以快速启动和部署。

面试时可以继续补充:它不是替代 Spring,而是简化 Spring 应用的创建、配置和运行。

2. 什么是自动配置?

Spring Boot 会根据项目依赖、配置文件和当前环境,自动创建常用 Bean。例如项目引入 Web 依赖后,可以自动配置 MVC 和内嵌 Tomcat。

常见追问:

  • 自动配置什么时候生效?
  • 如何关闭某个自动配置?
  • 自己定义 Bean 后,默认 Bean 是否还会创建?

3. Spring 中的 IOC 和 DI 是什么?

  • IOC:对象的创建和管理交给 Spring 容器。
  • DI:容器把对象依赖的其他对象注入进来。

常见注入方式有构造器注入、Setter 注入和字段注入。实际项目中优先使用构造器注入,依赖关系更明确,也更利于测试。

4. Bean 的生命周期大致是什么?

实例化 → 属性注入 → 初始化前处理 → 初始化方法 → Bean 可用 → 销毁前处理 → 销毁。

需要知道的扩展点包括 PostConstruct、PreDestroy、InitializingBean 和 BeanPostProcessor。

5. @Component、@Service、@Repository、@Controller 有什么区别?

它们本质上都是用于注册 Bean 的组件注解,但表达的职责不同:

  • Component:通用组件。
  • Service:业务逻辑层。
  • Repository:数据访问层。
  • Controller:Web 请求入口。

分层的意义是让职责清晰,便于维护、测试和替换实现。

6. @RestController 和 @Controller 的区别?

RestController 等价于 Controller 加上 ResponseBody,方法返回值默认会被序列化为响应体,适合提供 REST 接口。Controller 通常用于返回页面或视图。

7. Spring MVC 请求流程怎么说?

浏览器请求先到 DispatcherServlet,再由处理器映射找到 Controller,经过参数绑定和参数校验后执行方法,最后由消息转换器把返回对象序列化为 JSON。

面试表达重点:能说出 DispatcherServlet、Controller、Service、Mapper 的职责边界。

8. @Transactional 为什么可能不生效?

常见原因:

  • 方法不是 public。
  • 同一个类内部直接调用,绕过了 Spring 代理。
  • 异常被 catch 后没有继续抛出。
  • 抛出的是非配置的异常类型。
  • 数据库引擎不支持事务。
  • 事务注解放在了错误的层级或配置未生效。

排查时先看方法调用路径、代理是否经过、异常是否真正抛出,再检查数据库和配置。

9. Spring 中的 AOP 用来做什么?

AOP 用于把日志、权限、事务、监控等横切逻辑从业务代码中抽离出来。常见概念有切面、切点、通知和代理。

10. 配置文件和环境如何管理?

可以使用 application.yml、application-dev.yml、application-prod.yml 和 profile 区分环境。数据库密码、密钥等敏感配置不应直接提交到公共代码仓库。

11. 参数校验和全局异常怎么做?

  • 使用 Valid、NotNull、NotBlank、Size 等校验注解。
  • 在 Controller 参数上开启校验。
  • 使用 RestControllerAdvice 和 ExceptionHandler 统一转换异常响应。

重点是让接口返回稳定、统一、可定位的错误信息,而不是把堆栈直接返回给用户。

12. Spring Boot 项目如何排查接口 500?

建议按顺序排查:

  1. 查看请求参数和响应日志。
  2. 查看异常堆栈的最底层 cause。
  3. 确认 Controller、Service、Mapper 的调用路径。
  4. 检查 SQL、事务和数据库连接。
  5. 使用断点或增加结构化日志复现问题。

十、常用 Java 中间件

初级面试通常不要求背完整源码,但要知道每个组件解决什么问题、基本原理和常见风险。

1. Maven

用途:依赖管理、项目构建、测试和打包。

常见问题:

  • 依赖冲突如何排查?
  • clean、compile、test、package 有什么区别?
  • 为什么要锁定依赖版本?

排查依赖问题时可以查看 dependency tree,确认哪个依赖传递引入了冲突版本。

2. Redis

常见数据结构:String、Hash、List、Set、Sorted Set。

常见场景:

  • 缓存热点数据。
  • 保存验证码和登录状态。
  • 计数、排行榜和简单限流。
  • 分布式锁。

高频问题:

  • 缓存穿透、击穿、雪崩如何处理?
  • Redis 为什么快?
  • 如何保证缓存与数据库的一致性?
  • 分布式锁需要注意什么?

基础回答思路:穿透可以用缓存空值或布隆过滤器;击穿可以用互斥锁或逻辑过期;雪崩可以使用随机过期时间、限流和降级。分布式锁还要考虑过期时间、误删和续期。

3. RabbitMQ / Kafka

消息队列的主要作用:异步处理、流量削峰、系统解耦。

需要理解:生产者、消费者、队列、交换机、路由键、消费确认、重试、死信队列。

常见问题:

  • 消息重复消费怎么办?
  • 消息丢失如何处理?
  • 消息积压如何排查?
  • 如何保证业务幂等?

回答时要提到:消息不一定只消费一次,业务侧通常需要唯一业务编号、去重表、幂等校验或状态机。

4. MyBatis

虽然 MyBatis 更偏持久层框架,但在 Java 后端面试中非常常见。

重点掌握:

  • Mapper 接口与 XML / 注解映射。
  • 动态 SQL。
  • 一对多和多对多查询。
  • 分页和批量操作。
  • SQL 注入风险。

不要把复杂业务 SQL 全部堆在 XML 中,仍然要保持查询可读、可测试、可优化。

5. Nginx

常见用途:反向代理、负载均衡、静态资源服务、HTTPS 终止和限流。

面试中至少能解释:客户端请求先到 Nginx,再由 Nginx 转发到一个或多个 Spring Boot 服务。

6. Docker

需要掌握镜像、容器、端口映射、挂载和环境变量的基本概念。

常见问题:

  • 容器和虚拟机有什么区别?
  • 应用在本机能运行,容器中不能运行,如何排查?
  • 如何查看容器日志?

排查时重点检查端口、环境变量、配置文件、容器网络和启动日志。

7. Elasticsearch

如果岗位涉及搜索、日志或商品系统,可以补充学习:倒排索引、分词、全文检索、聚合查询和分页限制。

8. ZooKeeper

可以了解它在服务注册、配置管理、分布式协调和选主中的作用。初级岗位知道基本用途即可,不必一开始深入源码。

十一、多线程和并发面试题

1. 创建线程有哪些方式?

常见方式有:继承 Thread、实现 Runnable、实现 Callable 并配合 Future,以及直接使用线程池。

实际开发中优先使用线程池,因为线程池可以复用线程、控制并发数量、统一管理任务和拒绝策略。

2. 线程有哪些状态?

常见状态包括 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING 和 TERMINATED。

重点理解:

  • BLOCKED:等待进入 synchronized 监视器锁。
  • WAITING:无限期等待其他线程唤醒。
  • TIMED_WAITING:在指定时间内等待。

3. start() 和 run() 有什么区别?

调用 start() 会创建并启动新的线程,最终由新线程执行 run()。直接调用 run() 只是普通方法调用,不会创建新线程。

4. synchronized 的作用是什么?

synchronized 可以保证互斥访问,并提供一定的可见性和有序性保障。可以修饰实例方法、静态方法或代码块。

需要注意锁住的对象是谁,以及锁的范围是否过大。锁范围过大会降低并发性能。

5. volatile 能解决什么问题?

volatile 主要保证变量的可见性,并限制部分指令重排序,但不能保证复合操作的原子性。

例如 count++ 不是原子操作,不能仅靠 volatile 保证线程安全。需要使用 synchronized、Lock 或原子类。

6. synchronized 和 Lock 怎么选?

synchronized 语法简单、自动释放锁,适合大多数普通互斥场景。Lock 提供 tryLock、可中断、公平锁和多个条件队列等更灵活的能力,但必须在 finally 中释放锁。

7. 什么是 CAS?

CAS 是比较并交换:只有内存中的值等于预期值时,才更新为新值。它常用于原子类和无锁并发结构。

常见问题是 ABA 问题,即值从 A 变成 B 又变回 A。需要时可以使用带版本号的方案。

8. 线程池的核心参数有哪些?

ThreadPoolExecutor 常见参数包括:

  • corePoolSize:核心线程数。
  • maximumPoolSize:最大线程数。
  • keepAliveTime:非核心线程空闲存活时间。
  • workQueue:任务队列。
  • threadFactory:线程创建工厂。
  • handler:拒绝策略。

任务提交后通常会经历:核心线程处理 → 放入队列 → 创建非核心线程 → 执行拒绝策略。

9. 如何设置线程池大小?

不能只背一个固定数字,需要结合任务类型:

  • CPU 密集型:线程数通常接近 CPU 核数。
  • IO 密集型:可以适当高于 CPU 核数,但要结合等待时间、数据库连接数和下游承载能力。

还要通过压测和监控验证,避免线程数过大导致上下文切换、队列堆积或下游雪崩。

10. 线程池有哪些拒绝策略?

常见策略有:直接抛异常、由调用线程执行、丢弃任务、丢弃队列中最旧任务再尝试加入。

生产环境应根据业务重要性选择,不能无脑使用丢弃策略,否则可能造成业务数据丢失。

11. 什么是死锁?如何排查?

死锁通常需要同时满足互斥、占有且等待、不可剥夺、循环等待四个条件。

预防方法:

  • 固定加锁顺序。
  • 减少锁的持有时间。
  • 避免嵌套锁。
  • 使用 tryLock 设置超时。

排查可以查看线程 dump,搜索 BLOCKED、Found one Java-level deadlock 等信息。

12. ConcurrentHashMap 和 HashMap 的区别?

HashMap 不是线程安全的;ConcurrentHashMap 针对并发访问做了设计,可以在多个线程读写时提供更好的安全性和性能。

不要简单说成“ConcurrentHashMap 完全不会有问题”,复合业务操作仍然要考虑原子性,例如先判断再新增可能需要额外同步或原子 API。

13. CountDownLatch、CyclicBarrier、Semaphore 有什么区别?

  • CountDownLatch:等待一组任务完成,计数归零后继续,通常只能使用一次。
  • CyclicBarrier:一组线程互相等待,到达屏障后一起继续,可以循环使用。
  • Semaphore:控制同时访问某类资源的线程数量。

14. wait()、notify() 和 sleep() 的区别?

  • wait() 会释放当前对象锁,等待其他线程 notify 或 notifyAll。
  • notify() 唤醒一个等待线程,notifyAll() 唤醒全部等待线程。
  • sleep() 只让当前线程休眠,不会主动释放已经持有的锁。

调用 wait、notify 前必须持有对应对象的监视器锁。

15. 如何保证一个业务操作的线程安全?

先识别共享数据和临界区,再选择合适方案:不可变对象、局部变量、原子类、并发集合、synchronized、Lock、数据库唯一约束或分布式锁。

不要只在代码层加锁,还要确认数据库、缓存和消息队列中的状态是否会被并发修改。

十二、面试准备重点排序

如果时间有限,建议按这个顺序准备:

  1. Spring Boot 项目结构、IOC、AOP、MVC、事务和异常处理。
  2. MySQL、MyBatis、索引和常见 SQL。
  3. Redis 缓存和基本问题排查。
  4. 线程池、synchronized、volatile、Lock 和死锁。
  5. RabbitMQ 或 Kafka 的消息可靠性与幂等。
  6. Nginx、Docker、Maven、Linux 常用操作。

每学一个组件,都要回答三个问题:

  • 它解决什么问题?
  • 它的基本原理是什么?
  • 我在什么项目场景中使用过,遇到过什么问题?

这样准备出来的答案,比单纯背术语更容易通过追问。

十三、Spring Boot 专项面试题库

下面这部分可以作为面试前的重点复习清单。回答时建议采用“先给结论,再结合项目说明”的方式。

A. Spring Boot 基础

1. Spring、Spring MVC、Spring Boot 分别是什么?

  • Spring:提供 IOC、AOP、事务等核心能力。
  • Spring MVC:处理 Web 请求、参数绑定、视图或 JSON 响应。
  • Spring Boot:通过自动配置、起步依赖和内嵌服务器简化 Spring 项目开发。

2. Spring Boot 的核心优点有哪些?

自动配置、起步依赖、内嵌服务器、统一配置管理、健康检查和生产环境监控支持。它减少的是配置工作,不是取消对 Spring 原理的理解要求。

3. 什么是 starter?

Starter 是一组经过整理的依赖集合。例如 Web Starter 会引入 Spring MVC、JSON 序列化和内嵌服务器等常用依赖,避免开发者逐个添加。

4. SpringBootApplication 包含哪些注解?

它组合了 Configuration、EnableAutoConfiguration 和 ComponentScan:表示配置类、开启自动配置、扫描当前包及子包中的组件。

5. 为什么启动类一般放在根包下?

组件扫描默认从启动类所在包向下扫描。如果启动类位置过深,可能扫描不到 Controller、Service 或配置类。

6. 自动配置是如何实现的?

Spring Boot 根据 classpath 中的依赖、配置属性和条件注解判断是否创建默认 Bean。常见条件注解有 ConditionalOnClass、ConditionalOnMissingBean 和 ConditionalOnProperty。

7. 如何查看自动配置为什么生效或没有生效?

可以开启调试日志,查看条件评估报告;也可以检查依赖是否存在、配置项名称是否正确,以及是否被自定义 Bean 覆盖。

8. 如何排除某个自动配置?

可以在启动注解中配置 exclude,也可以通过配置文件排除。排除前要确认是否真的需要自己提供替代配置。

9. Bean 和普通 Java 对象有什么区别?

普通对象由代码自行创建和管理;Bean 由 Spring 容器创建、装配、初始化和销毁,可以被其他组件依赖注入。

10. Bean 默认是单例吗?

是。Spring 默认使用 singleton 作用域。其他常见作用域包括 prototype、request 和 session。单例 Bean 中不要保存请求级别的可变状态,否则可能产生并发问题。

11. ComponentScan 扫描不到 Bean,如何排查?

检查启动类包路径、组件注解、模块依赖和 profile 条件;确认类是否被 exclude,是否存在多个同名 Bean 或配置没有加载。

12. Autowired 和 Resource 有什么区别?

Autowired 主要按类型注入,可配合 Qualifier 指定名称;Resource 默认更偏向按名称查找。项目中应统一风格,并避免同一接口存在多个实现却没有明确指定。

13. 有多个同类型 Bean 时怎么办?

使用 Qualifier 指定 Bean 名称,或给一个实现加 Primary。更好的方式是让职责和命名清晰,避免依赖注入含义不明确。

14. Spring Bean 循环依赖是什么?

例如 A 依赖 B,同时 B 又依赖 A。构造器注入通常无法解决循环依赖;部分单例字段注入场景可能被提前暴露引用处理,但不应依赖这种机制,应该拆分职责或引入第三个协调服务。

B. Spring MVC 与接口开发

15. RequestParam、PathVariable、RequestBody 有什么区别?

  • RequestParam:读取查询参数,例如 /users?page=1。
  • PathVariable:读取路径变量,例如 /users/10。
  • RequestBody:读取 JSON 等请求体并转换为对象。

16. 参数校验如何实现?

在请求对象字段上使用 NotBlank、NotNull、Size、Email 等注解,在 Controller 参数上使用 Valid 或 Validated。统一异常处理器负责把校验错误转换成规范响应。

17. Valid 和 Validated 有什么区别?

Valid 属于 Bean Validation 的标准注解;Validated 是 Spring 提供的扩展,支持校验分组,也常用于方法级校验。

18. 如何统一返回结果?

可以定义统一响应对象,例如 code、message、data,并在 Controller 层统一使用。不要把数据库对象直接作为接口返回值,建议使用 DTO,避免暴露内部字段和形成强耦合。

19. 如何统一处理异常?

使用 RestControllerAdvice 配合 ExceptionHandler,分别处理参数错误、业务异常、资源不存在和未预期异常。日志中保留完整堆栈,返回给客户端的信息应简洁且不泄露敏感内容。

20. Filter、Interceptor、AOP 有什么区别?

  • Filter:Servlet 层能力,进入 Spring MVC 前后都可以处理。
  • Interceptor:Spring MVC 层能力,可以拿到 Handler 信息。
  • AOP:针对 Spring Bean 方法的横切逻辑,例如事务和业务日志。

21. 如何实现登录鉴权?

登录接口校验账号密码,成功后签发 Session 或 Token;后续请求通过 Filter 或 Interceptor 校验身份,把用户信息放入上下文;退出登录时失效凭证。敏感信息要加密保存,不能明文存储密码。

22. 如何处理跨域?

可以通过 CorsConfiguration、WebMvcConfigurer 或网关统一配置。生产环境应限制允许的域名、方法和请求头,不建议直接允许所有来源。

23. 文件上传需要注意什么?

限制文件大小和类型,生成服务端文件名,避免路径穿越和执行恶意文件;大文件应考虑对象存储和分片上传,不要全部堆在应用内存中。

C. 配置、环境与启动

24. application.properties 和 application.yml 怎么选?

两者都能配置 Spring Boot。YAML 更适合层级结构,properties 更直观。团队应统一格式,重点是配置命名、分环境和敏感信息管理。

25. 多环境配置如何实现?

使用 profile,例如 application-dev.yml、application-test.yml、application-prod.yml,再通过启动参数或环境变量激活。不要把生产密码写入代码仓库。

26. 配置注入有哪些方式?

可以使用 Value 注入单个值,也可以使用 ConfigurationProperties 绑定一组配置。配置较多时优先使用 ConfigurationProperties,并配合校验保证启动时尽早发现错误。

27. 配置优先级怎么理解?

Spring Boot 会从命令行参数、环境变量、配置文件等多个来源读取配置。实际排查时要确认最终生效值,避免本地配置覆盖了预期环境变量。

28. 如何优雅关闭 Spring Boot 应用?

应用停止时应停止接收新请求,等待正在处理的任务完成,再释放线程池、数据库连接和消息消费者。部署环境要配置合理的停止等待时间。

29. Actuator 有什么用?

Actuator 提供健康检查、指标、应用信息和运行状态端点,便于监控和排查。生产环境必须限制端点暴露范围并做好鉴权。

D. 数据库、事务与 MyBatis

30. Transactional 的传播行为是什么?

传播行为决定当前方法有事务时,被调用方法如何加入或创建事务。常见的 Required 会加入当前事务,没有则创建新事务;RequiresNew 会挂起当前事务并创建独立事务。

31. Transactional 的隔离级别是什么?

隔离级别用于控制并发事务之间的可见性,主要解决脏读、不可重复读和幻读问题。实际使用要结合数据库默认设置和业务一致性要求,隔离越强通常并发代价越高。

32. Transactional 为什么通常放在 Service 层?

Service 层代表一个完整业务操作,可能调用多个 Mapper。把事务放在这里可以覆盖完整业务边界,而不是只包住一条 SQL。

33. 事务失效有哪些常见原因?

自调用绕过代理、方法不是 public、异常被吞掉、异常类型不匹配、事务方法使用了错误的数据源、异步线程脱离原事务上下文。

34. MyBatis 中井号参数和美元参数有什么区别?

井号参数会使用预编译参数,能降低 SQL 注入风险;美元参数是字符串拼接,只能在确实需要动态表名、排序字段且经过白名单校验时使用。

35. 如何排查慢 SQL?

先确认是否确实是 SQL 慢,再用执行计划查看索引使用情况,检查查询条件、排序、分页、回表和返回数据量。最后结合慢查询日志、应用日志和数据库监控定位。

36. 分页查询为什么可能越来越慢?

传统 offset 分页在页码很大时需要扫描并跳过大量记录。数据量大时可考虑基于递增 ID 或时间字段的游标分页,并确保查询条件有合适索引。

E. 缓存、消息与异步

37. Spring Cache 常用注解有哪些?

常见注解包括 Cacheable、CachePut、CacheEvict 和 Caching。使用前要明确缓存 key、过期时间、更新策略和缓存穿透处理方式。

38. 缓存更新和数据库更新的顺序怎么选?

常见做法是先更新数据库,再删除缓存;读取时缓存不存在再查询数据库并回填。高并发场景还要考虑并发读写、延迟双删和消息最终一致性。

39. Async 使用有什么注意点?

异步方法通常通过代理执行,同类内部直接调用可能不会异步。要配置专用线程池,设置队列容量和拒绝策略,不能直接使用无限制的默认线程池。

40. 消息消费失败如何处理?

根据业务设置重试、延迟重试和死信队列;记录失败原因和业务编号。消费者必须具备幂等性,避免重试造成重复扣库存或重复发货。

F. 测试、部署与排障

41. Spring Boot 如何做单元测试和接口测试?

业务纯逻辑优先使用普通单元测试;需要加载 Spring 容器时使用 SpringBootTest;Controller 层可以使用 MockMvc。测试应覆盖正常流程、参数错误、异常和边界条件。

42. MockBean 有什么用?

MockBean 可以在 Spring 测试上下文中替换真实依赖,隔离外部服务、数据库或消息组件,使测试更快、更稳定。

43. 应用启动失败如何排查?

先看最底层异常原因,再检查端口占用、配置文件、环境变量、数据库连接、依赖版本和 Bean 创建错误。不要只看最后一行 Application failed to start。

44. 应用运行一段时间后变慢,如何排查?

观察 CPU、内存、GC、线程数、连接池、数据库慢查询、接口耗时和消息积压。结合日志、监控、线程 dump 和必要的堆 dump,先确认瓶颈再优化。

45. 如何设计一个可靠的 Spring Boot 接口?

做好参数校验、鉴权、幂等控制、事务边界、超时设置、异常转换、日志追踪和监控指标。接口文档中明确请求、响应、错误码和重试规则。

46. 如何提高 Spring Boot 接口性能?

先通过监控确认瓶颈,再从 SQL 和索引、缓存、批量操作、连接池、线程池、序列化、分页和网络调用等方向优化。避免在没有数据依据时盲目加缓存或加线程。

47. 如何保证接口幂等?

可以使用业务唯一号、数据库唯一约束、幂等记录表、Token 或分布式锁。幂等设计要覆盖超时重试、消息重复投递和用户重复点击等情况。

48. 如何回答“你最熟悉的 Spring Boot 项目是什么”?

按“业务背景 → 技术架构 → 个人职责 → 一个具体难点 → 排查过程 → 最终结果”的顺序回答。准备一个真实接口,能画出调用链:

客户端 → 网关 / Nginx → Controller → Service → Mapper / Redis → MySQL / 消息队列

面试官继续追问时,重点讲清楚事务、异常、并发、缓存和失败重试,而不是只罗列技术名称。

十四、Spring Boot 面试前速记清单

  • 能解释 IOC、AOP、自动配置和 Bean 生命周期。
  • 能说清 MVC 请求流程和参数校验方式。
  • 能独立完成统一响应、统一异常和日志处理。
  • 能解释事务边界、传播行为和事务失效原因。
  • 能使用 MyBatis 编写安全、可优化的 SQL。
  • 能说明 Redis 缓存问题和消息消费幂等。
  • 能配置线程池、连接池和优雅停机。
  • 能使用日志、监控、线程 dump 排查线上问题。
  • 简历中写到的每个注解和组件,都准备一个实际使用例子。

推荐模拟面试顺序

  1. 先用 2 分钟介绍项目。
  2. 面试官随机问 5 个 Spring 基础题。
  3. 再问 3 个事务、SQL 或缓存问题。
  4. 最后做 1 个线上故障排查场景。

每次模拟面试后,把答不上来的问题按“不会概念、会概念但不会应用、做过但表达不清”分类,下一轮只针对最薄弱的一类复习。

十五、PHP 常见运行模式

PHP 面试中说“运行模式”,通常是指 PHP 如何接收 Web 请求、如何被 Web 服务器调用,以及脚本如何执行。常见模式如下。

1. CLI 模式

CLI(Command Line Interface)是在命令行中直接运行 PHP,例如:

php script.php

常见场景:定时任务、数据迁移、队列消费者、脚本工具和本地调试。CLI 不经过 Nginx 或 Apache,也没有一次请求对应一次响应的限制。

2. CGI 模式

CGI 是 Web 服务器与 PHP 解释器之间的通用协议。每次请求通常会启动一个新的 PHP CGI 进程,请求结束后进程退出。

优点是实现简单、隔离性较好;缺点是每次创建进程开销较大,并发性能较差,因此现代生产环境较少直接使用传统 CGI。

3. FastCGI 模式

FastCGI 对 CGI 做了改进:PHP 进程常驻,通过进程池复用,Web 服务器把请求转发给空闲的 PHP 进程处理。

优点是减少频繁创建进程的开销、性能更好,也便于 Web 服务器和 PHP 服务独立部署。Nginx 通常通过 FastCGI 与 PHP-FPM 通信。

4. PHP-FPM 模式

PHP-FPM(FastCGI Process Manager)是 PHP 常用的 FastCGI 进程管理器。它负责管理 Worker 进程、监听端口或 Unix Socket、控制进程数量、处理慢请求和优雅重启。

典型链路是:

客户端 → Nginx → FastCGI → PHP-FPM Worker → PHP 脚本

常见面试追问:

  • PHP-FPM 和 FastCGI 的关系是什么?
  • PHP-FPM 进程数如何配置?
  • PHP-FPM 慢请求如何排查?

可以回答:FastCGI 是通信协议,PHP-FPM 是实现该协议并负责进程管理的 PHP 服务。进程数要结合 CPU、内存、请求耗时和数据库连接数压测确定,不能盲目调大。

5. Apache module(mod_php)模式

在 Apache 中,PHP 可以作为模块加载,由 Apache 直接处理 PHP 请求。配置简单,但 PHP 与 Web 服务器耦合较紧,进程模型和资源隔离不如 Nginx + PHP-FPM 灵活。

6. 常见对比题

模式 进程特点 常见场景 主要问题
CLI 命令行启动脚本 定时任务、脚本 不直接处理 Web 请求
CGI 每次请求可能创建进程 早期 Web 部署 创建进程开销大
FastCGI 进程常驻并复用 Web 服务 需要进程管理器
PHP-FPM 管理 FastCGI Worker Nginx + PHP 需要合理配置进程池
mod_php Apache 内嵌 PHP 模块 Apache 项目 耦合度较高

7. PHP 运行模式面试回答模板

PHP 在命令行中可以使用 CLI 模式;Web 场景早期常见 CGI,现在生产环境通常采用 Nginx + FastCGI + PHP-FPM。FastCGI 是通信协议,PHP-FPM 负责管理常驻 Worker 进程,通过进程复用提升性能。具体进程数需要结合机器资源和压测结果配置。

十六、MySQL 两种常见存储引擎及区别

面试中最常比较的是 InnoDB 和 MyISAM。现在新项目一般优先使用 InnoDB,但仍要理解 MyISAM 的历史特点。

1. InnoDB

InnoDB 是 MySQL 默认存储引擎,支持事务、行级锁、外键和崩溃恢复。它使用聚簇索引组织主键数据,主键设计会影响查询和写入性能。

适合订单、支付、库存、用户等需要事务和并发更新的业务。

2. MyISAM

MyISAM 结构简单,早期查询性能和存储效率较好,但不支持事务、不支持外键,主要使用表级锁,崩溃恢复能力也弱于 InnoDB。

在读多写少、对事务要求低的旧系统或特定只读场景中可能见到,但不建议作为新业务的默认选择。

3. InnoDB 与 MyISAM 对比

对比项 InnoDB MyISAM
事务 支持 不支持
锁粒度 主要是行级锁,也有表级锁 主要是表级锁
外键 支持 不支持
崩溃恢复 较强,支持 redo log 等机制 相对较弱
并发写入 较好 写操作容易阻塞整张表
索引组织 聚簇索引,数据按主键组织 非聚簇索引,数据和索引相对分离
适用场景 事务业务、高并发读写 旧系统、读多写少、非事务场景

4. 高频追问

为什么 InnoDB 更适合订单系统?

订单创建通常涉及多个表和多个写操作,需要事务保证一致性;库存扣减还可能产生并发更新,行级锁比表级锁更适合。

InnoDB 的行级锁一定不会锁表吗?

不一定。没有合适索引、范围条件不当或锁升级等情况,可能锁住大量记录甚至影响整表。间隙锁、临键锁也可能扩大锁定范围。

MyISAM 查询一定比 InnoDB 快吗?

不能绝对比较。实际速度取决于查询、索引、数据量、并发和硬件。InnoDB 通过缓存、索引和并发控制,在多数现代业务场景中已经足够快。

如何查看表使用的存储引擎?

可以查看建表语句或表信息,例如:

SHOW CREATE TABLE user;
SHOW TABLE STATUS LIKE 'user';

十七、Java 中进程、线程和协程的区别

1. 进程

进程是操作系统进行资源分配和隔离的基本单位。每个进程通常拥有独立的地址空间、堆、文件描述符和系统资源。

特点:

  • 隔离性较强,一个进程崩溃通常不会直接破坏另一个进程。
  • 创建、销毁和进程间通信的成本较高。
  • 进程之间共享数据需要 IPC、Socket、管道或共享内存等机制。

2. 线程

线程是进程中的执行流,是操作系统调度的基本单位。一个进程可以拥有多个线程,同一进程内的线程共享堆、方法区和文件资源,但有各自的栈和程序计数器。

特点:

  • 创建和切换成本通常低于进程。
  • 线程之间共享内存,通信方便,但容易出现竞态、死锁和数据可见性问题。
  • Java 中的 Thread、线程池和并发工具主要对应这一层。

3. 协程

协程是更轻量的任务调度单位,通常由语言运行时或框架在用户态调度。协程可以在执行到 I/O 等待时主动让出执行权,再恢复到原来的位置。

Java 传统线程模型中没有像 Go 那样的内置协程;现代 Java 可以使用虚拟线程(Virtual Thread)降低大量 I/O 阻塞任务的线程成本,也可以使用 Reactor、Kotlin 协程等方案实现异步或协作式编程。面试时不要把协程简单等同于线程。

4. 三者对比

对比项 进程 线程 协程
资源归属 独立资源和地址空间 隶属于进程,共享进程资源 通常运行在线程之上,共享运行时资源
调度者 操作系统 操作系统 运行时、框架或用户代码
切换成本 高 中等 通常较低
隔离性 强 较弱 较弱
通信方式 IPC、Socket、共享内存 共享内存、并发队列 通道、消息、挂起与恢复机制
适合场景 服务隔离、独立任务 CPU 并发、常规并发编程 大量 I/O、异步流程、轻量任务

5. 进程、线程、协程面试常见问题

进程和线程有什么区别?

进程拥有独立资源和地址空间,隔离性强但切换和通信成本高;线程是进程内的执行单元,共享进程资源,切换成本更低,但共享数据需要处理线程安全。

协程为什么适合 I/O 密集型场景?

I/O 等待期间,协程可以挂起当前任务,让出执行权给其他任务,减少线程阻塞和上下文切换,从而用较少的线程处理更多并发请求。

协程一定比线程快吗?

不一定。协程主要降低调度和等待成本,不能让 CPU 计算本身变快。CPU 密集型任务仍需要合理利用 CPU 核数,协程过多也会增加调度和内存开销。

Java 虚拟线程和传统线程有什么区别?

虚拟线程由 JVM 调度,创建和阻塞成本更低,适合大量 I/O 阻塞任务;传统平台线程通常对应操作系统线程,资源成本更高。虚拟线程不是让 CPU 计算能力增加,也不能替代数据库连接池、限流和业务幂等设计。

多线程共享数据如何保证安全?

根据场景选择不可变对象、局部变量、原子类、并发容器、synchronized、Lock、线程安全队列或数据库约束。先识别共享数据和临界区,再决定同步范围,避免无依据地加大锁粒度。

6. 一句话记忆

进程重在资源隔离,线程重在执行并发,协程重在轻量调度;三者不是完全互斥的关系,协程通常仍然运行在线程或线程池之上。