一、基础概念题
1、Java基础
问题:ArrayList和LinkedList有什么区别?在什么场景下应优先选择LinkedList?
答案:
区别:
底层结构:ArrayList基于动态数组实现,支持快速随机访问;LinkedList基于双向链表实现,插入和删除效率高。
时间复杂度:
ArrayList的
get(int index)
为O(1),但插入/删除(非尾部)为O(n);LinkedList的
get(int index)
为O(n),但头尾插入/删除为O(1)。
内存占用:LinkedList每个节点需额外存储前后指针,空间开销更大。
场景选择:
优先选择LinkedList的场景:频繁在列表中间或头部进行插入/删除操作;
不需要频繁随机访问,且对内存不敏感。
2、多线程
问题:如何实现线程安全的List?CopyOnWriteArrayList的底层原理是什么?
答案:
线程安全List的实现方式:
使用
Collections.synchronizedList(new ArrayList<>())
包装;使用
CopyOnWriteArrayList
(适用读多写少场景)。
CopyOnWriteArrayList原理:
写时复制:每次修改(如
add
、set
)时,复制原数组生成新数组,操作完成后替换原数组。无锁读:读操作直接访问原数组,无需加锁;
写锁:写操作通过
ReentrantLock
保证同步,避免并发写导致数据不一致。
3、JVM内存模型
问题:Java内存模型中,堆(Heap)和栈(Stack)的区别是什么?
答案:
堆(Heap):
所有线程共享,存放对象实例和数组;
需要垃圾回收(GC)管理内存;
可能发生内存溢出(OOM)。
栈(Stack):
线程私有,存放局部变量、方法调用和基本类型变量;
栈帧随方法调用创建/销毁,自动内存管理;
可能发生栈溢出(StackOverflowError)。
4、数据库索引
问题:数据库索引的作用是什么?哪些情况下索引会失效?
答案:
作用:
加速数据检索;
唯一索引保证数据唯一性;
通过索引排序减少磁盘I/O。
索引失效场景:
对索引列进行运算(如
WHERE age+1=20
);使用前导通配符的LIKE(如
LIKE '%abc'
);违反最左前缀原则(联合索引未从第一列开始查询);
类型隐式转换(如字符串列用数字查询);
OR条件中非索引列参与。
5、网络协议
问题:HTTP和HTTPS的主要区别是什么?HTTPS如何保证数据传输安全?
答案:
区别:
安全性:HTTPS通过SSL/TLS加密传输,HTTP明文传输;
端口:HTTP默认80,HTTPS默认443;
证书:HTTPS需CA证书验证服务器身份。
HTTPS安全机制:
非对称加密:握手阶段通过RSA等算法交换对称密钥;
对称加密:后续通信使用对称密钥加密数据;
数字证书:防止中间人攻击,验证服务器真实性。
6、设计模式
问题:什么是单例模式?如何实现线程安全的懒汉式单例?
答案:
单例模式:确保一个类仅有一个实例,并提供全局访问点。
线程安全懒汉式实现:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
关键点:双重检查锁(Double-Check Locking)+ volatile
禁止指令重排序。
7、操作系统
问题:进程和线程的区别是什么?
答案:
进程:
资源分配的最小单位,独立地址空间;
进程间切换开销大,需切换内存映射;
进程间通信需IPC(如管道、共享内存)。
线程:
CPU调度的最小单位,共享进程资源;
线程切换开销小;
需通过锁、信号量等机制保证同步。
8、安全防护
问题:什么是SQL注入?如何防止SQL注入?
答案:
QL注入:攻击者通过构造恶意输入,篡改SQL语句逻辑(如
' OR 1=1 --
)。防御措施:
预编译(PreparedStatement):参数化查询,避免拼接SQL;
输入过滤:对用户输入进行转义或白名单验证;
最小权限原则:数据库账号避免使用高权限角色;
ORM框架:如MyBatis,自动处理参数绑定。
二、框架与工具题
1、Spring核心概念
问题:Spring框架的核心功能是什么?解释控制反转(IoC)和依赖注入(DI)的区别与联系。
答案:
核心功能:
IoC容器:管理对象的生命周期和依赖关系;
AOP(面向切面编程):通过代理实现日志、事务等横切关注点的解耦;
简化开发:如JDBC、事务管理等模板化封装;
集成支持:与其他框架(如Hibernate、MyBatis)无缝整合。
IoC与DI的区别与联系:
IoC(控制反转):将对象创建和依赖管理的控制权交给容器,而非程序员硬编码。
DI(依赖注入):是实现IoC的具体方式,通过构造函数、Setter方法或接口注入依赖对象。
联系:DI是IoC的实现手段,IoC是DI的设计目标。
2、Spring MVC工作流程
问题:描述Spring MVC处理HTTP请求的完整流程。
答案:
DispatcherServlet接收请求:作为前端控制器,统一处理所有HTTP请求。
HandlerMapping路由:根据请求URL找到对应的Controller。
Controller处理请求:执行业务逻辑,返回
ModelAndView
(数据+视图名)。ViewResolver解析视图:将视图名转换为具体视图(如JSP、Thymeleaf)。
视图渲染:将模型数据填充到视图中,生成响应内容。
返回响应:通过DispatcherServlet返回给客户端。
关键组件:
拦截器(Interceptor):在请求前后执行(如权限校验);
数据绑定:将请求参数映射到方法参数(如
@RequestParam
)。
3、Spring Boot自动配置
问题:Spring Boot如何实现“约定优于配置”?举例说明自动配置的典型场景。
答案:
实现原理:
条件注解:如
@ConditionalOnClass
(当类路径存在某类时生效);spring.factories:通过META-INF/spring.factories定义自动配置类;
默认配置:基于依赖的JAR包自动启用功能(如内嵌Tomcat)。
典型场景:
内嵌Web服务器:添加
spring-boot-starter-web
后自动配置Tomcat;数据源:当检测到
DataSource
依赖时,自动配置连接池;JPA:自动配置
EntityManagerFactory
和事务管理器。
4、常用注解对比
问题:解释@Controller、@RestController、@Service、@Repository的作用及使用场景。
答案:
**@Controller**:
作用:标记为Spring MVC控制器,处理HTTP请求;
场景:返回视图(如JSP)时使用,需配合视图解析器。
**@RestController**:
作用:
@Controller
+@ResponseBody
,直接返回JSON数据;场景:开发RESTful API时使用。
**@Service**:
作用:标记业务逻辑层组件;
场景:封装复杂业务逻辑,如事务管理。
**@Repository**:
作用:标记数据访问层组件,处理持久化异常(如将SQL异常转换为Spring统一异常);
场景:DAO层实现(如JPA、MyBatis Mapper)。
5、Spring Boot配置文件
问题:Spring Boot支持哪些类型的配置文件?如何在不同环境中切换配置?
答案:
配置文件类型:
**.properties文件**:如
application.properties
;**.yml/.yaml文件**:层次化配置,语法更简洁。
多环境配置:
按环境命名:创建
application-{profile}.properties
(如application-dev.properties
);激活环境:
命令行参数:
--spring.profiles.active=prod
;配置文件:
spring.profiles.active=dev
;
优先级:外部配置 > 命令行参数 > 当前JAR包内配置。
三、中级应用与设计题
1、JWT鉴权流程
问题:解释JWT的鉴权流程及如何结合Spring Security实现动态权限控制。
答案:
JWT鉴权流程:
用户登录:客户端发送用户名/密码到服务端;
生成Token:服务端验证通过后,使用密钥生成JWT(包含用户信息、权限、过期时间);
返回Token:客户端存储Token(通常存于LocalStorage或Cookie);
请求携带Token:后续请求在
Authorization
头中携带JWT;验证Token:服务端校验签名、过期时间,并解析用户信息;
权限校验:根据Token中的权限信息,决定是否允许访问接口。
结合Spring Security实现动态权限:
自定义
JwtAuthenticationFilter
:拦截请求,解析并验证JWT,生成Authentication
对象;实现
UserDetailsService
:从数据库加载用户权限(如角色、菜单权限);动态权限配置:通过
@PreAuthorize
注解或SecurityConfig
中的antMatchers()
动态匹配权限;权限数据源:将权限规则存储在数据库,启动时加载到
SecurityMetadataSource
。
2、Redis缓存雪崩
问题:Redis出现缓存雪崩如何应对?
答案:
缓存雪崩场景:大量缓存同时失效,导致请求直接穿透到数据库,引发服务崩溃。
解决方案:
随机过期时间:为缓存设置基础过期时间 + 随机值(如
30分钟 + random(0, 600)
秒);互斥锁(Mutex Lock):缓存失效时,使用分布式锁(如Redis的
SETNX
)限制只有一个线程重建缓存;热点数据永不过期:对高频访问数据,仅更新值不删除,后台异步刷新;
双层缓存策略:一级缓存(本地缓存,如Caffeine)短时间失效,二级缓存(Redis)长周期失效;
熔断降级:通过Hystrix等组件对数据库访问限流或降级;
缓存预热:服务启动时加载高频数据到缓存。
3、Docker Compose部署
问题:如何通过Docker Compose一键部署10个微服务?举例说明关键配置。
答案:
关键配置:
version: '3.8'
services:
service1:
image: registry/service1:latest
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
networks:
- my-network
depends_on:
- redis
- mysql
# 其他服务(service2到service10)配置类似
redis:
image: redis:alpine
ports:
- "6379:6379"
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
networks:
my-network:
driver: bridge
核心要点:
服务定义:每个微服务指定镜像、端口、环境变量;
依赖管理:通过
depends_on
声明服务启动顺序(如先启动数据库);共享网络:所有服务加入同一网络,通过服务名互相访问(如
redis:6379
);一键启动:执行
docker-compose up -d
启动全部服务。
4、单元测试覆盖率
问题:如何保证单元测试覆盖率?如何推动改进核心接口覆盖率不足?
答案:
保证覆盖率的方法:
工具集成:使用JaCoCo或SonarQube统计覆盖率;
CI/CD约束:在流水线中设置覆盖率阈值(如≥80%),否则阻断发布;
测试分层:结合单元测试(JUnit)、集成测试(TestContainers)、契约测试(Pact);
代码审查:在PR中要求新增代码必须包含测试用例。
改进核心接口覆盖率不足:
用例补全:针对核心接口的分支、异常场景补充测试;
Mock外部依赖:使用Mockito隔离数据库、第三方API等依赖;
重构代码:将复杂逻辑拆分为可独立测试的小单元;
团队培训:通过分享会或文档规范测试编写方法;
奖惩机制:将覆盖率指标纳入KPI考核。
5、MQ幂等性与脑裂
问题:如何实现RabbitMQ消息的幂等性?如何避免集群脑裂导致的数据不一致?
答案:
消息幂等性:
唯一消息ID:生产者为每条消息生成唯一ID,消费者记录已处理ID(如Redis Set);
数据库去重:业务表增加
msg_id
字段并设置唯一索引;版本号控制:消息携带数据版本号,仅处理更高版本的操作;
状态机校验:业务逻辑中判断当前状态是否允许执行操作(如订单已支付则不重复处理)。
避免集群脑裂:
镜像队列:使用RabbitMQ镜像队列(HA Queue)同步数据到多个节点;
仲裁队列(Quorum Queue):基于Raft协议保证数据一致性;
节点隔离策略:配置
cluster_partition_handling=pause_minority
,使少数节点自动暂停;网络心跳检测:通过高可用网络和监控工具(如Keepalived)快速发现分区;
人工干预恢复:脑裂后手动确认数据一致性,再恢复节点。
四、高级系统设计题
高并发架构设计
问题:设计支持每秒10万次数据上报的服务端架构,如何保证数据不丢失且低延迟?
答案:分布式一致性
问题:如何保证高并发下单场景下的数据一致性(如库存扣减)?
答案:JVM调优实战
问题:如何定位GC频繁问题?举例说明JVM参数优化过程。
答案:线上故障排查
问题:线上服务突然出现大量HTTP 500错误,如何快速定位?若数据库连接池耗尽如何解决?
答案:微服务网关设计
问题:设计微服务网关需考虑哪些核心功能?如何实现动态路由配置?
答案:
五、开放与行为题
1、职业规划
问题:未来3年的职业目标是什么?若对当前技术方向不感兴趣如何调整?
答案:
未来3年目标:
短期(1年内):深入当前技术栈(如高并发、分布式系统),主导或参与核心项目,积累复杂场景的实战经验;
中期(1-2年):向架构师角色过渡,提升系统设计能力(如性能优化、容灾设计),主导跨团队协作;
长期(3年):根据兴趣选择技术深耕(如云原生、AI工程化)或转向技术管理(如团队搭建、技术规划)。
调整技术方向的策略:
自我评估:明确兴趣点(如数据分析、前端可视化),通过小项目验证可行性;
学习迁移:利用现有技术基础横向扩展(如Java转大数据开发学习Spark/Flink);
内部转岗:与团队沟通,争取参与目标方向的边缘项目,逐步过渡;
外部尝试:通过开源贡献、技术社区分享或副业探索新领域,降低试错成本。
2、团队协作
问题:与同事对技术方案有分歧(如是否引入Redis)如何解决?
答案:
解决步骤:
数据驱动:用性能压测结果(如MySQL QPS瓶颈)、业务场景(如缓存高频读低频写)佐证方案;
小规模验证:搭建POC环境对比两种方案(如Redis缓存命中率 vs 数据库直接查询);
寻求共识:邀请资深同事或架构师参与讨论,综合技术成本(如运维复杂度)、长期扩展性评估;
妥协记录:若无法达成一致,优先满足核心需求并记录分歧点,后续迭代中验证优化。
关键态度:
避免技术“站队”,聚焦业务目标;
尊重对方专业视角,避免人身攻击;
接受“最优解可能不存在”,选择可落地的平衡方案。