【中级Java面试总结】
面试总结
- 1Java面试
-
- 1.1数据库优化?如何提高sql查询效率?
- 1.2假设有个图书馆系统,同时有很多人去预约用同一个座位,请问这个并发操作如何设计保证不会出现重复预约这种错误?
- 1.3在开发中怎么防止数据重复提交
- 1.4开发过程中用过哪些设计模式?
- 1.5介绍一下工厂模式?它是怎么解耦合的?为什么能解耦合
- 1.6多线程的实现方式有哪些?
- 1.7Runnable、Thread、Callable有啥区别?你的物流更新定时任务用的哪一种?
- 1.8Java线程中什么资源不能共享?什么资源能共享?
- 多线程处理任务时,有些线程会失败,它的数据没有获取到,你设计的多线程是如何解决这种问题的?
- 1.9出现大并发的交易情况时,拒绝策略对系统的稳定性比较重要,你对拒绝策略有了解吗?以往的项目重使用过哪些拒绝策略?
- 1.10线程池的核心参数有哪些?如何配置?
- 现在有一个线程池,最大线程数是10个,队列的长度是100个,核心线程数满了之后,它会如何处理?是先扩展你的线程数还是把这队列的100个长度占满?
- 1.11现在多个20W记录的文件数据需要导入到数据库,可能每次处理10个或5个,怎么实现批量提交?大文件怎么读取?分批上传怎么保证数据不重复读取?
- PDF500兆,这个文件数据如何抽取?采用流式处理,如果网络断了,只处理了一部分,剩下的一部分怎么办?
- 1.12常用的集合有哪些?它的底层结构是什么?
- 1.13Redis如果遇到了雪崩和穿透应该怎么处理?你的项目经验里什么时候会考虑用Redis?讲一讲你对Redis分布式锁的理解
- Redis的单机并发量只有10万,项目中实际并发量有100万,Redis集群怎么部署?
- 1.14如何保证Rabbit的MQ不丢失?如何防止消息重复消费?请结合你的项目经验说明
- 你这个日志脱敏是怎么实现的?
- 你说你对接了一些下游系统,怎么对接的?
- 你这个日终推送订单文件怎么推送的?你仔细讲讲
- Spring Cloud的核心组件和作用
- Spring Boot的启动流程和配置原理
- Mybatis的#和\$不同匹配符的区别?什么时候用#{}什么时候用\${}
- 如何保证接口的幂等性
- 2发散题
- 综合题
- 3.前端
- 4.面试心得
- 5.面试经历
1Java面试
1.1数据库优化?如何提高sql查询效率?
- 建立合适索引,避免索引失效;
- 经常作为 where 条件 的字段建索引
- 联合索引遵循最左匹配原则
- 避免索引失效,提高命中率
- 大字段、重复度高的字段不建索引
- 优化 SQL 语句,*避免 select 、避免函数运算、避免全表扫描;
- 避免 select *,只查需要的字段
- 避免 !=、not in、is null 导致索引失效
- 避免在索引字段上做 函数运算、隐式类型转换
- 小表驱动大表,合理使用 join
- 避免深度子查询,改用 join 或关联查询
- 分页优化:大 offset 用主键 id 过滤
- 优化表结构,选择合适字段类型,适度冗余;
- 避免全表扫描
- 避免大事务、长事务
- 避免频繁的 order by / group by 无索引排序
- 避免大量数据一次性查询,使用分页或分批处理
- 数据量大时做 读写分离、分库分表;
- 字段类型合理:int 优于 varchar,时间用 datetime
- 大字段拆分到副表
- 适度冗余,减少 join
- 避免过多 null 字段
- 开启慢查询日志,持续定位并优化慢 SQL
定位慢 SQL 后:EXPLAIN 分析执行计划(优化核心)
拿到慢 SQL,前缀加EXPLAIN看索引、扫描类型、回表、文件排序:
EXPLAIN SELECT id,order_no FROM t_order WHERE user_id=10086 AND status=1 ORDER BY create_time DESC LIMIT 20;
- 重点看 type 字段(性能从优到劣)
const > eq_ref > ref > range > index > ALL
ALL:全表扫描,严重问题,必须建索引
range:范围查询(in/>=/between),合理
ref:普通索引命中,正常 - 重点关注其他风险字段
key:实际使用的索引,NULL = 没走索引
rows:预估扫描行数,数字越大越慢
Extra:
Using filesort:SQL 出现 ORDER BY 无索引,文件排序,IO 爆炸
Using temporary:GROUP BY 无索引,创建临时表
Using index:覆盖索引,最优(无需回表)
- 引入缓存减轻数据库压力;
1.2假设有个图书馆系统,同时有很多人去预约用同一个座位,请问这个并发操作如何设计保证不会出现重复预约这种错误?
核心是利用数据库行锁,通过一条带状态判断的 update 语句: 更新座位状态时加上 where
status=0,只有空闲才能预约成功。 InnoDB 会对主键加行锁,保证同一时间只有一个请求能修改成功,从根本上避免超卖。
高并发场景下,可以配合 Redis 分布式锁 或 乐观锁版本号 进一步控制并发,减轻数据库压力。
最终保证:一个座位同一时间只能被一个用户预约成功。
1.3在开发中怎么防止数据重复提交
防止重复提交一般从前后端一起控制: 前端通过按钮禁用、loading 避免重复点击; 后端主要用 Redis
存储唯一提交令牌,提交时先占锁,占锁成功才执行业务; 同时对关键业务表建立 数据库唯一索引 做最终兜底; 高并发接口还会结合
分布式锁 保证接口幂等,彻底避免重复提交问题。
1.4开发过程中用过哪些设计模式?
单例模式用于工具类和全局配置;
工厂模式和策略模式处理多种贵金属业务类型,避免过多 if-else;
模板方法统一订单流程;
适配器模式对接上海金币第三方接口;
观察者模式处理订单状态变更后的消息推送; 还有建造者模式用于构建复杂对象。
1.5介绍一下工厂模式?它是怎么解耦合的?为什么能解耦合
工厂模式就是由一个工厂类统一负责创建对象,调用方不需要直接去 new 具体实现类。 它把对象的使用和对象的创建分离开,调用方只依赖工厂接口,不依赖具体实现,
当实现类变化或新增类型时,调用方代码不需要修改,从而实现了解耦,让代码更容易维护和扩展。
1.6多线程的实现方式有哪些?
Java 多线程实现方式主要有四种: 继承 Thread 类、实现 Runnable 接口、实现 Callable 接口(支持返回值),
以及企业开发最常用的 线程池 ExecutorService
start () 和 run () 的区别:
- start () 会启动新线程,由系统调度执行 run (),实现多线程。
- run () 只是业务方法,直接调用不会开启线程,还是同步执行。
1.7Runnable、Thread、Callable有啥区别?你的物流更新定时任务用的哪一种?
- Thread 是类,单继承受限;
- Runnable 无返回值、不能抛异常;
- Callable 有返回值、能抛异常,功能更强。
物流更新属于后台定时同步任务,没有返回值需求,所以采用 Runnable 实现任务逻辑,交由线程池异步执行,避免阻塞主线程。
为什么用 Runnable?
- 物流更新只是异步同步状态、推送消息,不需要返回值
- 不需要异常抛出后获取结果,只需要记录日志即可
- 定时任务 + 线程池,最适合用 Runnable
- 轻量、简单,符合场景
1.8Java线程中什么资源不能共享?什么资源能共享?
- 堆内存的对象、静态变量、类信息等是可以共享的;
- 线程栈里的局部变量、方法参数、程序计数器是线程私有的,不能共享。
- 共享资源会存在线程安全问题,需要通过加锁来保证安全。
- 栈是线程私有,为了保证方法执行独立、互不影响,也不需要线程安全。
- 堆是线程共享,为了让线程之间能传递数据、协作完成任务。
- 但共享就会有线程安全问题,需要加锁:synchronized、Lock、volatile 等。
拓展:
JVM的数据结构?什么情况下会出发FullGC?
JVM 运行时数据区分为 5 大区域,分为线程私有和线程共享两类:
- 线程私有(每个线程独立,不会 GC)
- 程序计数器(PC Register):记录当前线程执行到哪一行字节码,不会 OOM,不会 GC。
- 虚拟机栈(VM Stack):
(1)方法调用时创建栈帧,存储局部变量、方法出口、操作数栈。
(2)异常:StackOverflowError / OOM。 - 本地方法栈(Native Method Stack):给 native 方法(如 C/C++)用,和虚拟机栈类似。
- 线程共享(会被 GC,会 OOM)
- 堆(Heap):
(1)存放所有对象实例、数组,GC 主要发生在这里。
(2)分为:新生代(Eden + Survivor) + 老年代。 - 方法区(Method Area)
(1)存储类信息、常量、静态变量、即时编译代码。
(2)JDK8+ 叫 元空间(Metaspace),使用本地内存。
(3)还有运行时常量池也在这里。
- 堆(Heap):
FullGC = 对整个堆(新生代 + 老年代 + 元空间)进行全局垃圾回收
触发 FullGC 的 7 种核心场景(核心是老年代不够用和GC 机制异常)
- 老年代空间不足
对象从新生代晋升到老年代,老年代放不下 → 直接 FullGC。 - 元空间(Metaspace)不足
大量动态生成类(反射、CGLib、JSP)导致元空间满 → FullGC。 - 显示调用 System.gc ()
代码里写 System.gc() 只是建议GC,JVM 大概率执行 FullGC。
⚠️ 生产环境必须禁止! - Minor GC 后晋升对象大于老年代剩余空间
新生代回收后,存活对象要进入老年代,但老年代空间不够 → FullGC。 - CMS 并发收集时出现 Concurrent Mode Failure
并发 GC 过程中,业务线程产生的对象太快,老年代提前满了 → 触发 FullGC。 - 空间分配担保失败
新生代 GC 前,JVM 判断:
历次晋升平均大小 > 老年代剩余空间
为了安全,直接触发 FullGC。 - 堆内存异常(OOM)前最后一次回收
JVM 抛出 OOM 之前,会最后执行一次 FullGC 尝试释放空间。
记忆口诀:老年代满、元空间满、调 gc、担保失败、并发失败、晋升失败、OOM 前
扩展知识:
- 悲观锁:先上锁再操作,保守、安全、性能一般。
- 乐观锁:不加锁,靠版本号 / CAS 判断冲突,高并发友好。
- 分布式锁:跨 JVM、跨机器的全局锁,解决分布式并发问题。
- CAS 原理
CAS 即比较并交换,通过 CPU 原子指令实现无锁并发。它包含内存值、旧预期值、新值,只有当内存值等于旧值时才更新,否则重试。优点是无锁高并发,缺点是高并发写会自旋耗 CPU,且存在 ABA 问题。 - ABA 问题
ABA 是指一个值从 A 变 B 再变回 A,CAS 无法察觉中间被修改过。解决方案是增加版本号或时间戳,Java 提供 AtomicStampedReference 解决。
多线程处理任务时,有些线程会失败,它的数据没有获取到,你设计的多线程是如何解决这种问题的?
我们多线程同步物流信息时,如果线程执行异常、数据没获取到, 首先会在任务内部捕获异常,然后自动重试 3 次;
仍然失败就把任务信息记录到数据库, 再通过定时任务每隔几分钟扫描并重新执行; 核心链路还会配合 MQ
消息机制,确保任务不丢失、数据最终一致。
1.9出现大并发的交易情况时,拒绝策略对系统的稳定性比较重要,你对拒绝策略有了解吗?以往的项目重使用过哪些拒绝策略?
在高并发交易、下单、预约场景下,我们使用的是 CallerRunsPolicy 拒绝策略。
为什么选它?
- 不抛异常,不影响交易接口稳定性
- 不丢失任务,不会出现用户下单失败、丢单
- 让主线程去执行,相当于自然限流、压力缓冲,保护线程池不被打满
- 系统压力越大,提交速度越慢,从而保护数据库和核心服务

1.10线程池的核心参数有哪些?如何配置?
我们项目是 IO 密集型的交易系统,线程池配置大致如下: corePoolSize:根据 CPU 核数 * 2 设置
maximumPoolSize:设为核心线程的 1.5~2 倍 workQueue:使用有界队列
ArrayBlockingQueue,防止队列无限堆积 keepAliveTime:60 秒 拒绝策略:使用
CallerRunsPolicy,保证高并发下不丢单、系统稳定
现在有一个线程池,最大线程数是10个,队列的长度是100个,核心线程数满了之后,它会如何处理?是先扩展你的线程数还是把这队列的100个长度占满?
任务进来 核心线程数未满 → 新建核心线程执行 核心线程已满 → 先放进队列排队 队列也放满了(100 个满了) →
才开始创建非核心线程,直到达到最大线程数 10 队列满 + 最大线程也满 → 触发拒绝策略
线程的工作流程:先核心线程->再队列->再最大线程->最后拒绝策略
当调用excute(task)时
- 判断核心线程数(corePoolSize)
- 当前线程数<核心线程数:直接创建核心线程执行任务;
- 否则进入下一步
- 判断工作队列(workQueue)是否已满
- 队列没满:把任务放入队列排队
- 队列满了:进入下一步
- 判断最大线程数(maximumPoolSize)
- 当前线程数<最大线程数:新建非核心线程执行任务
- 否则进入下一步
- 执行拒绝策略(RejectedExecutionHandler)
- 队列满了、线程也满了:触发拒绝策略(抛异常/丢弃当前或最老任务/调用者执行)
1.11现在多个20W记录的文件数据需要导入到数据库,可能每次处理10个或5个,怎么实现批量提交?大文件怎么读取?分批上传怎么保证数据不重复读取?
大文件读取:使用流式读取,逐行解析,避免一次性加载导致 OOM。
批量提交:设置批次大小(如 1000 条),攒够一批执行一次批量插入,提升入库效率。
防重复读取:通过文件 MD5 标识 + 记录读取偏移量,实现断点续传;同时给数据建立唯一索引做兜底,保证数据不重复
PDF500兆,这个文件数据如何抽取?采用流式处理,如果网络断了,只处理了一部分,剩下的一部分怎么办?
对于 500 兆的大 PDF 文件,我会采用按页流式解析,使用 PDFBox 或 iText 逐页读取处理,避免一次性加载导致内存溢出。
如果处理过程中网络中断或程序异常退出, 我会通过记录已处理页码的方式实现断点续传, 程序重启后直接从中断的页码继续处理,不需要从头开始;
同时给数据建立唯一索引做兜底,保证数据不重复、不丢失。
核心方案:断点续传 + 记录已处理页码
1.给 PDF 生成唯一标识
- 文件 MD5 / 文件大小 + 文件名,作为唯一 key
2.数据库记录处理进度
- 建一张表:
- file_md5
- file_name
- current_page(当前处理到第几页)
- status(处理中 / 成功 / 失败)
3.每次处理完一页就更新进度
4.重启后从断点继续
- 程序重启 → 读取 current_page → 直接从下一页开始处理
5.兜底:业务唯一键去重
解析的数据生成唯一键,数据库建唯一索引,防止重复插入
1.12常用的集合有哪些?它的底层结构是什么?
常用集合主要有: List:ArrayList(动态数组)、LinkedList(双向链表)
Set:HashSet(HashMap)、TreeSet(红黑树) Map:HashMap(数组 + 链表 +
红黑树)、TreeMap(红黑树)、LinkedHashMap 其中最常用的是 ArrayList、LinkedList、HashMap。
HashMap
底层:数组 + 链表 + 红黑树
JDK1.8 之前:数组 + 链表 (哈希冲突头插法,容易相互指引容易形成环产生死循环)
JDK1.8 及以后:链表长度≥8 转红黑树,≤6 退化为链表(哈希冲突尾插法)
线程不安全,允许 key/value 为 null;
哈希冲突:两个不同的 key,通过哈希算法计算后得到了相同的哈希值 / 数组下标,就叫哈希冲突。
Q:如何解决哈希冲突?
JDK 1.7:数组 + 链表
- 冲突时,在对应数组位置头插法形成链表。
JDK 1.8:数组 + 链表 + 红黑树
- 冲突先拉链,形成链表
- 链表长度 ≥ 8 且数组长度 ≥ 64 → 转为红黑树
- 红黑树节点 ≤ 6 → 退化为链表
Q:为什么HashMap线程不安全? 因为其底层结构是决定其不安全,没有添加任何锁,多线程下做put/resize时,会出现: 数据覆盖;元素丢失;
JDK1.7会出现 链表成环,死循环(CPU 100%);
JDK1.8改为尾插法解决了死循环,但依然没有锁,数据覆盖、丢失问题仍然存在,所以仍然线程不安全;
Q:那如何保证线程安全呢? 可以使用ConcurrentHashMap,JDK1.7 分段锁,JDK1.8 CAS + synchronized
1.13Redis如果遇到了雪崩和穿透应该怎么处理?你的项目经验里什么时候会考虑用Redis?讲一讲你对Redis分布式锁的理解
缓存雪崩:大量 key 同时过期,或 Redis 宕机,所有请求直接打数据库,导致数据库压垮。
解决方案:
- 给过期时间加随机值,避免同一时间批量失效
- 做集群、主从、哨兵,保证高可用
- 开启多级缓存(本地缓存 Caffeine + Redis)
- 接口增加限流、降级、熔断
- 数据库层加锁,防止被打挂
缓存穿透:查询根本不存在的数据,缓存不命中,每次都查数据库。
解决方案:
- 不存在的数据也缓存 空值,并设置短暂过期时间
- 使用布隆过滤器,过滤不存在的 key
- 前端和参数校验拦截非法请求
Q:项目里什么时候会用 Redis?
- 高频查询数据做缓存:比如库存信息、产品信息、用户信息、定时任务基础数据,减少数据库压力。
- 分布式锁:抢座、预约、下单、重复提交控制,用 Redis 锁保证并发安全。
- 防止重复提交:用 Redis 存储请求唯一标识,短时间过期。
- 限时业务:预约有效期、验证码过期、订单超时未支付。
- 计数器、限流:接口限流、短信发送频率控制
Redis分布式锁:在分布式、多实例部署下,保证同一时间只有一个服务能执行某段代码,解决并发安全问题,通过 SET NX EX 命令实现互斥与自动过期,防止死锁;
雪崩:当大量key同时失效,请求直接访问数据库导致数据库崩溃,可以随机过期时间,或者熔断和降级只保留一些核心交易,一些非核心交易返回默认值;
穿透:热key失效,大量请求直接访问数据库,发生崩溃,可以设置热key永不过期,或者随机过期时间;
击穿:大量请求访问不存在的key,直接击穿数据库,可以把查询过的没有的值置为null存入Redis,增加布隆过滤器对非法值进行校验和过滤
补充熔断、降级、限流
7. 熔断:当下游服务错误率过高时,直接切断调用,不再请求,快速失败。
2.降级:当下游服务超时、报错/自身服务压力大、大促或突增流量时,关闭非核心功能返回默认值,仅保留核心功能;
3.限流:当出现突增流量,控制接口单位时间内的请求数量,防止流量过大把服务打垮。
扩展Redis的常用数据类型以及应用场景
1.String:用于存储字符串类型比如token
- 缓存:用户信息、商品详情、配置信息
- 计数器:点赞数、阅读数、接口访问次数
- 分布式锁:SET key value NX EX
- 验证码、token 存储
2.List:列表用来存储消息队列、任务队列
3.Hash: 存储结构化对象,用于存储产品信息等
- 对象缓存:用户信息、订单信息(结构化存储,比 String 更省内存)
- 购物车:用户 ID → (商品 ID: 数量)
4.set:无序集合,去重,可以用于点赞、订阅等场景
5.ZSet:有序集合,用于排行榜:积分、销量、热度排序,限流、优先级队列
Redis的单机并发量只有10万,项目中实际并发量有100万,Redis集群怎么部署?
- 单机 Redis 只能支撑约 10 万并发,面对 100 万并发需要使用 Redis Cluster 集群部署。
- 集群采用 多主多从架构,比如 10 主 10 从; 内部通过 16384 个哈希槽 做数据分片,key 根据槽位路由到不同主节点,实现并发水平扩展;
- 同时每个主节点搭配从节点做主从复制,配合自动故障转移保证高可用。 这样整体并发能力可以随主节点数量线性提升,轻松支撑百万级并发。
1.14如何保证Rabbit的MQ不丢失?如何防止消息重复消费?请结合你的项目经验说明
你这个日志脱敏是怎么实现的?
自定义注解+AOP+脱敏工具类 首先自定义脱敏注解,标在需要脱敏的字段上,比如手机号、身份证、银行卡。 然后写一个 AOP 切面,拦截 Controller 和 Service 的方法。 在打印日志之前,我先拿到要输出的对象, 通过反射遍历里面所有字段, 看看哪个字段上加了脱敏注解, 有的话就把中间几位替换成星号,
处理完之后再打印日志,这样日志里就不会出现明文了
注解放字段上,用AOP 拦截 Controller,用反射遍历字段,判断有没有注解,有就替换成星号,再set 回去
参考代码:
1.自定义注解
// 元注解就两个:Target 放字段上,Retention 运行时生效
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitize {
// 类型:手机号、身份证、银行卡
String type();
}
2.实体类上加注解
public class User {
// 不加注解,不脱敏
private Long id;
// 加了就脱敏
@Desensitize(type = "PHONE")
private String phone;
@Desensitize(type = "ID_CARD")
private String idCard;
}
3.AOP核心逻辑
@Aspect
@Component
public class DesensitizeAspect {
// 切点:所有 Controller
@Pointcut("execution(* com.xxx.controller..*.*(..))")
public void pointcut() {}
// 环绕通知
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 执行目标方法
Object result = joinPoint.proceed();
// 2. 返回值为空直接返回
if (result == null) {
return null;
}
// 3. 反射遍历所有字段,看有没有 @Desensitize
Field[] fields = result.getClass().getDeclaredFields();
for (Field field : fields) {
// 判断是否有脱敏注解
if (field.isAnnotationPresent(Desensitize.class)) {
field.setAccessible(true);
String value = (String) field.get(result);
// 根据类型脱敏
Desensitize anno = field.getAnnotation(Desensitize.class);
String type = anno.type();
String newValue = switch (type) {
case "PHONE" -> desensitizePhone(value); // 138****1234
case "ID_CARD" -> desensitizeIdCard(value); // 110***********1234
default -> value;
};
// 把脱敏后的值塞回去
field.set(result, newValue);
}
}
// 4. 返回脱敏后的对象
// 日志打印的就是处理后的结果
return result;
}
}
4.脱敏工具方法
// 手机号脱敏
private String desensitizePhone(String phone) {
if (phone == null || phone.length() != 11) return phone;
return phone.substring(0, 3) + "****" + phone.substring(7);
}
// 身份证脱敏
private String desensitizeIdCard(String idCard) {
if (idCard == null || idCard.length() < 10) return idCard;
return idCard.substring(0, 6) + "**********" + idCard.substring(idCard.length()-4);
}
你说你对接了一些下游系统,怎么对接的?
- 先确认对接方式,我们常用 HTTP/HTTPS 接口 或者 MQ 消息 对接;
- 双方约定好接口地址、参数、返回格式、签名和加密规则;
- 把对方给的 appId、secret、密钥 配到
配置中心,不写死在代码里;- 自己这边封装一个调用工具类,用 RestTemplate 发请求,处理参数、加签名;
- 调用之后解析返回结果,判断成功失败;
- 加上超时、重试、幂等、日志,保证调用可靠、不重复、不出问题。
用 MQ 怎么对接?
比如订单、结算这种异步场景, 我们系统把消息发到 RabbitMQ, 下游系统监听队列消费消息, 消息体做脱敏和校验,
保证消息不丢、不重复消费。
补充:
Kafka:适合高吞吐和实时流处理场景,如日志采集和大数据分析,百万级亿级。
RocketMQ:适合需要事务支持和高可靠性的场景,如电商和金融业务。
RabbitMQ:适合复杂路由和企业集成场景,如任务调度和延迟任务,十万级。
你这个日终推送订单文件怎么推送的?你仔细讲讲
我们系统每天日终跑完批处理之后,会生成当天的业务订单文件。 如果是大文件,我会先按固定大小,比如每片 4MB
进行分片处理,每一片都带上文件唯一标识、当前分片序号和总分片数。
然后把这些分片通过SFTP推送到中间中转系统,中转系统负责接收和临时存储。
上传过程中,如果某一个分片失败了,就只重传这一个分片,成功的不再重复传,一般重试 2~3 次。
等所有分片都上传成功,中间系统会根据序号把分片合并成一个完整文件,再做 MD5 校验,确保文件没有损坏。
校验通过之后,下游绩效系统就可以直接到中间系统去拉取完整文件,再进行解析和处理。
整个过程我们都会记录文件状态、上传时间、分片结果,方便后续排查问题。
Spring Cloud的核心组件和作用
Spring Boot的启动流程和配置原理
Mybatis的#和$不同匹配符的区别?什么时候用#{}什么时候用${}
如何保证接口的幂等性
2发散题
2.1平时用一些什么智能工具,能够辅助提升开发效率的?如何使用AI大模型的?
2.2了解cursor吗?用过cursor吗?
2.3在以往的工作重遇到过哪些难题?如何解决的?
在现有的系统中集成AI,让你去做你会怎么做?
引入AI需要注意什么?
综合题
部署服务,假如说有4个节点,有一个节点宕机了,给你发力一个告警信息,你如何去跟踪这个问题?如果是内存溢出导致的如何跟踪处理?什么情况下会导致内存溢出?
现在有一些订单需要提交,订单需要用多线程去处理,然后订单处理过程中需要判断这个订单是否是一个风险订单,判断条件是如果该订单价格超过100W,它就是一个风险订单,如何设计?(编写代码)
git合并如何解决冲突以及实际项目中对分支的要求?
Git工具操作的开发流程
描述一个你比较熟悉的CICD的流程
3.前端
前端有多个vue文件,这些vue文件之间传递数据,有哪些传递方法?
前端表单提交如何必反重复提交造成数据污染?
前端渲染比较慢怎么解决?
4.面试心得
4.1自我介绍
面对不同面试官自我介绍也有不同的门道,
面试官是HR时:介绍自己就大概介绍自己干过哪种行业经验,比如干过银行开发,干过医疗项目,主要在于HR问啥答啥就是,基本都是看是否匹配招聘基本要求。
面试官是技术面试:自我介绍着重介绍项目,介绍项目用了哪些技术做了哪些功能,尽量详细一些
面试官是领导面试:自我介绍着重介绍自己的丰功伟绩,比如考过哪些证书参加过哪些比较有含金量的比赛,突出自己的软实力,领导更看重产出和问题解决能力,介绍自己尽量结构层次清晰的描述自己比如(基本情况->项目经验->自我评价【尽量和面试要求扯上关系】)
面试官,您好,我是xx,来自xxx,毕业于xxx学的专业是xxx,目前有x年的Java开发经验,
面试官是群面时:这时候不要慌张,这里面应该包含了技术和领导一起面试,这时候可以在详细介绍项目的时候顺便突出自己的软实力,比如开发xx功能从头到尾负责与跟踪,包括文档编写与业务沟通等,这种群面就等不同的人问一一回答就差不多了。
5.面试经历
5.1华芯数智-Java后端
军工类项目,注重数据采集和高并发和部署项目相关经验

一面:
技术一面
先自我介绍
1.你在项目中如何保证幂等性?
(1). 接口层:防用户重复提交(订单/开票)
场景:用户重复点击、网络重试,导致重复下单、重复开票
方案:业务唯一ID + Redis分布式锁(SETNX)
- 前端请求时传入业务唯一标识(订单号、发票申请流水号)
- 后端接口第一步:以该唯一ID为Key, SETNX 加锁,过期时间 > 业务执行时长
- 加锁成功才执行业务,失败直接返回「已处理」
- 业务执行完毕手动释放锁
(2). MQ消费层:RabbitMQ重复消费(数据同步/流水)
场景:MQ重试、网络重传导致消息重复消费,数据重复插入
方案:消息唯一ID + Redis去重表 + MySQL唯一索引兜底
- 每条消息携带唯一消息ID/业务流水号
- 消费前先查Redis:存在则直接ACK丢弃,不存在再执行业务并写入Redis
- 数据库层给流水号/订单号建唯一索引,作为最后防线
(3). 定时任务层:Quartz重复执行(对账/批量同步)
场景:集群部署、任务重试导致定时任务重复执行
方案:Redis分布式锁抢占 + 任务状态标记
- 任务启动前先抢分布式锁,只有抢到锁的节点执行
- 执行时更新任务状态为「执行中」,执行完改为「已完成」
- 重复任务检测状态直接跳过
日终数据对账、批量数据导入的Quartz任务,靠这个防止重复统计、重复入库。
(4). 数据库层:强一致性兜底(核心写入)
方案1(写入):MySQL唯一索引(订单号、发票号、流水号)
重复插入直接触发唯一冲突,捕获异常返回成功,强兜底、绝对幂等
方案2(更新):乐观锁(版本号)
UPDATE xxx SET … WHERE version = 旧版本
只有匹配才执行,重复更新不生效
总结:1. 前端防重 → 2. 接口Redis锁 → 3. MQ去重 → 4. 定时任务锁 → 5. DB唯一索引兜底
任何一层都能保证幂等,最后数据库是绝对安全防线,完全满足分布式、高并发下的幂等要求。
2.遇到订单并发是怎么做的?
订单并发核心就两件事:防超卖、防重复下单、保证数据一致性。我们项目用 Redis 预扣库存 + 分布式锁 + 数据库事务 + 唯一索引兜底
四层控制,高并发下既保证性能,又绝对不超卖、不重复下单。
- 前端+网关层:基础防重
- 按钮点击置灰,防止连续点
- 生成订单唯一幂等号(前端请求时带)
- 接口层:Redis 分布式锁(核心)
以 用户ID+商品ID/订单号 做锁 key
SET lock_key value NX PX 3000
- 加锁成功才进下单逻辑
- 失败直接返回「订单处理中」
作用:同一用户同一时间只能下一单,从源头拦住并发重复提交
- 库存扣减:Redis 预扣 + 异步/事务同步
高并发绝对不直接查DB扣库存!
流程:
1). 商品库存存在 Redis
2). decr 原子扣减
3). 扣减成功 → 再异步/事务同步到DB
4). 扣减失败(<0)直接返回库存不足
- 数据库层:最终兜底(绝对安全)
1). 事务包裹:创建订单 + 扣库存 同一事务
2). 唯一索引:订单号、流水号唯一约束,重复插入必报错
3.HashMap的底层数据结构?
JDK 1.8 之后,HashMap 底层是数组 + 链表 + 红黑树。
- 数组是主体,叫哈希桶(table)
- 哈希冲突用链表解决
- 链表太长会树化成红黑树,提高查询效率
- 当链表长度 ≥ 8,且数组容量 ≥ 64 时,链表转为红黑树
当红黑树节点数 ≤ 6 时,退化为链表(把查询复杂度从 O(n) 降到 O(log n))
JDK 1.8 的 HashMap 底层是数组+链表+红黑树。数组是哈希桶,用来定位元素;发生哈希冲突时用链表解决;当链表长度超过8且数组容量大于等于64时,链表会转化为红黑树,降低查询时间复杂度。哈希冲突解决、扩容机制、树化与退化是核心要点。
4.项目中有用到分库分表吗?
我们项目里核心订单表、流水表、发票表因为数据量增长很快,单表千万级后查询和写入都变慢,所以做了分库分表。 采用的是
Sharding-JDBC 客户端分片,按照 用户ID 哈希取模
做水平分表,同时配合订单号范围分片做历史数据归档,解决单表过大、性能瓶颈问题。
- 分片策略(最关键)
- 水平分表为主:按 user_id % 8 分成 8 张订单表
- t_order_0 ~ t_order_7
- 为什么用 user_id?
- 同一个用户的订单在同一张表,查询聚合最方便
- 数据分布均匀,避免热点
- 组件
- 用 Sharding-JDBC
- 无侵入,只改配置,不用改业务代码
- 配合解决的问题
- 单表数据量控制在 2000万以内
- 写入TPS提升,查询走分片键,不走全表扫描
- 配合读写分离,查询走从库
- 全局唯一ID
分表后不能用自增ID,我们用:
- 雪花算法(Snowflake) 生成全局唯一订单号
- 保证跨表唯一、趋势递增
- 分页/跨表查询怎么处理
- 带分片键(user_id):Sharding-JDBC自动路由到对应表,性能极高
- 不带分片键(后台管理):广播查询 + 内存归并,控制分页大小,避免慢查询
5.Linux服务上做过项目部署吗?
做过,我们项目是Java后端应用,主要在CentOS 7上部署。
我负责过打包、上传、启停、日志排查、端口占用、服务监控整套流程,包括JVM参数调优、Nginx反向代理、Redis、MySQL、RabbitMQ环境维护都参与过。
比如打包后使用 Xshell 上传服务器,重启服务
- 查进程: ps -ef | grep java
- 杀进程: kill -9 进程号
- 启服务:nohup java -jar xxx.jar
- 查端口: netstat -tunlp | grep 端口
- 实时日志: tail -f server.log
- 查磁盘: df -h
- 查内存CPU: top 、 free -h
二面:
面试官三个技术、HR、领导,面试先来了个自我介绍,然后问了
1.介绍比较熟的项目以及比较熟悉的流程还问高并发下怎么保证库存扣减的
我说的上海金币购买的流程从手机银行到贵金属到上海金币,用了mq推送数据,会同步上海金币产品库存数据,把产品库存刷到缓存不用频繁查库,扣减也是在缓存进行,然后异步去DB扣减库存,实际上的库存兜底也会有上海金币,所以高并发下不会存在超卖的情况
2.高并发下数据幂等性如何保持
使用唯一标识键加Redis分布式锁,如果订单请求来了,先加锁setnx,请求加锁失败的话就表示已处理订单,加锁成功就正常处理,然后再手动释放锁就行了
3.和上海金币的请求封装是用的什么协议
我说是用jason格式,因为用的专线直连,所以就没有考虑什么权限了之类的
- 通用型接口
一般用 HTTP/HTTPS 协议,数据封装用 JSON,也就是现在主流的 RESTful 接口。
优点是通用、跨语言、易调试、兼容性好,适合对外服务、第三方对接、前后端交互。
- 内部微服务 / 高性能调用
内部服务之间更多用RPC框架,比如 gRPC、Dubbo。
底层基于 TCP 或 HTTP/2,数据封装用 Protobuf、Hessian 这类二进制协议。
优点是传输更快、序列化体积小、性能更高,适合公司内部微服务之间调用。
`系统之间的调用必须做访问权限控制,主要分两层:
- 接口权限(身份认证)
内部服务调用:一般用Token、JWT、签名验证
对外接口:用AppKey + AppSecret + 签名或 OAuth2.0
目的:确认调用方是合法的。 - 访问控制(谁能调)
内部微服务:通过服务鉴权、IP 白名单、网关控制
外部系统:必须 申请开通、分配权限、限流
目的:只允许授权的系统 / 服务调用,防止非法访问。
4.问平时调什么大模型?有用一些国外的模型吗?
我说千义
有些模型付费都是调着玩的没有实际应用到工作中,在工作中没有用过国外的模型,因为需要考虑安全性方面的问题,比如前段时间比较火的小龙虾就有安全隐患问题,个人电脑上部署学习可以,但是工作上还是要考虑安全隐患这些方面,二就是有些模型需要付费使用,个人用的比较少
5.如何使用ai工具协助进行代码开发
平时用cursor和GitHub
coplitor进行代码补全,cursor则用来进行需求开发,描述需求逻辑和边界以及开发风格等之类的,插件则是用来开发一些简单的重复的工作,有时候也用来看一下别人写的代码
6.那你使用cursor的流程是什么
一般都是需求下来之后我先自己分析和理解需求之后然后去进行开发,然后描述成ai能听懂的语句,最好是列个12345这样的,给agent描述清楚逻辑,然后等待生成的代码,查看生成的代码后查看是否是自己想要的,验证ai生成的逻辑是否有问题,有则进行调整,然后测试然后提交前把所有代码都过一遍,以确保没有安全注入风险之类的问题,让别人也帮忙审查一下代码,没问题就提交
5.2三盾科技-Java后端
是一个国企面试,线下去笔试然后两个面试官提问
笔试题
1.谈一谈你对工厂模式的理解,举例说明你在项目中的使用
工厂模式是创建型设计模式的一种,它将对象的创建与使用分离,由专门的工厂类负责封装对象创建逻辑,调用者不需要知道具体创建细节,只需要通过统一入口获取对象即可。核心优势是降低代码耦合,提升扩展性——新增对象类型时,只需要修改工厂逻辑即可,不需要大面积修改业务代码。
根据封装层次不同,工厂模式分为三类:
简单工厂:一个工厂类,根据入参决定创建哪一种具体对象,适合产品类型少、变化少的场景
工厂方法:定义抽象工厂接口,每个具体产品对应一个具体工厂,符合开闭原则,但会增加类数量
抽象工厂:围绕超级工厂创建其他工厂,负责生成一系列相关的产品对象,适用于多产品族的场景
使用场景如:产品类型(代销、经销、自研)或者订单支付渠道(微信、支付宝、等)可以封装为简单工程,无需关心具体创建细节,业务代码只需要拿到产品类型或者支付渠道就可以让工厂创建对应实例。
常用设计模式:
- 单例模式(最常用)
项目里工具类、配置常量、全局缓存、线程池配置,都会用饿汉 / 懒汉 + 双重锁单例。
保证全局只有一个对象,节约内存、避免重复创建,比如:配置读取、SDK 客户端实例、redis 工具类。 - 工厂模式(简单工厂 + 工厂方法)
做业务分支、多品类、多类型逻辑解耦必用。
比如医疗 / 订单业务:
不同检查项目、不同订单类型,通过工厂统一产出对象,不用大量 if-else;新增类型直接加实现类,符合开闭原则。 - 策略模式(业务优化神器)
对接多种支付、多种审批流程、多种报表导出、多种状态流转:
把每个分支逻辑抽成独立策略类,上下文统一调度。
彻底消灭臃肿 if else,后期维护、新增规则特别清爽,我重构老代码经常用这个模式。 - 模板方法模式
统一固定流程,个性化步骤自定义。
比如:导入导出模板、消息推送(短信 / 钉钉 / 公众号)、定时任务通用执行流程;
父类定骨架,子类只重写差异化方法,代码复用性极高。 - 装饰器模式 / 适配器模式
适配器:对接第三方老旧接口、不同系统字段兼容,做适配转换,不用改原有核心代码;
装饰器:给原有功能动态加增强,比如加日志、加权限校验、加缓存兜底,不侵入原业务逻辑。
用设计模式的核心就两点:
- 解耦、消灭 if-else;
- 面向开闭原则,易扩展、易维护,尤其微服务 / 复杂业务场景,代码可读性和迭代效率高。
2.什么是RESTful,其设计原理是什么?
RESTful 不是协议,是基于 HTTP、面向资源的接口设计规范,全称是表现层状态转移。 它核心设计原理就四点:
- 一切皆资源:业务实体用名词定义 URL,不用动词;
- HTTP 动词做操作:GET 查、POST 增、PUT 全改、PATCH局部改、DELETE 删,语义统一;
- 无状态:服务端不存会话,每次请求带全凭证,方便集群扩容、微服务调用;
- 用 JSON做表现层状态交互,配合标准 HTTP 状态码返回结果,结构清晰、前后端好对接。
开发里我们会严格控制 URL 只用名词、GET 不改数据、PUT/DELETE 保证幂等,登录鉴权用 Token放在请求头,完全贴合无状态。
3.什么是线程安全?线程不安全的场景,请举例说明
线程安全就是多线程并发操作共享数据时,最终结果依然正确、不会被覆盖错乱。
线程不安全本质是多线程同时读写同一个可变资源,像i++、ArrayList、HashMap 并发操作都会出问题,因为操作不具备原子性,会互相覆盖丢数据。
实际开发里,计数器、普通集合、Spring 单例 Bean
的成员变量最容易踩坑;尤其金融账务、余额扣减、积分优惠券,一旦线程不安全会造成资金错账、超发,必须用锁或者并发原子类、分布式锁保证安全。
线程安全:多线程同时操作共享资源(变量 / 对象 / 数据库数据),不管线程怎么调度、交替执行,最终结果永远和「单线程串行执行」的正确结果一致,不会出现脏数据、错乱、覆盖,就是线程安全。
反过来:多线程并发改共享变量,结果乱了、数据丢了、被覆盖了 → 线程不安全
Java 多线程三大问题:
- 共享可变资源:多个线程读 + 改同一个变量;
- CPU 缓存可见性:一个线程改了,另一个线程看不到;
- 指令重排 + 原子性破坏:i++这种操作不是一步完成(读 - 改 - 写三步),多线程穿插就乱
线程不安全经典案例
- 最简单 i++ 计数器 :对一个共享变量进行并发加减
- Java 集合线程不安全
ArrayList / HashMap / HashSet 全是线程不安全:
(1)多线程同时 add ArrayList → 数组越界、数据覆盖、丢元素;
(2)多线程同时 put HashMap → 死循环、数据丢失、CPU 飙高(JDK1.8 前链表环)。
✅ 安全替代:
List → CopyOnWriteArrayList
Map → ConcurrentHashMap - 全局共享对象没加锁
怎么解决?
- 简单变量:synchronized / Lock / AtomicInteger(原子类)
- 集合:用 J.U.C 并发容器
- 分布式业务(金融):加分布式锁(Redis/ZooKeeper)
- 尽量少用「单例 Bean 里的可变成员变量」
AtomicInteger原理、synchronized 和 Lock 区别
AtomicInteger 底层是 volatile 保证可见性,配合 CAS 自旋实现无锁原子更新,靠 CPU 硬件指令,适合单纯数值计数,并发性能高,但高竞争会空转耗 CPU,还存在 ABA 问题。
synchronized 是 JVM 内置悲观锁,JDK1.6 有锁升级,使用简单自动释放,适合锁住一段代码保证多变量安全。
ReentrantLock 是基于 AQS 的手动锁,比 synchronized 灵活,支持公平锁、超时、中断、多条件唤醒,但需要手动解锁。简单计数用 AtomicInteger,代码块同步优先 synchronized,复杂并发控制用 Lock。
4.#{}和${}的区别
- #{} 是 MyBatis 预编译占位符,底层用 PreparedStatement,自动转义、加引号,能防 SQL 注入,日常 CRUD 都用它;
- ${} 是直接字符串拼接,没有预编译,存在严重 SQL 注入风险,只在动态传表名、字段、排序字段时不得已使用,而且必须做参数白名单校验。
5.编写sql给了一张表编写谁的销售额高 排名以及销售额

函数区别按需换:
- RANK():同分同名次,跳号(1,1,3)
- DENSE_RANK():同分同名次,不跳号(1,1,2)
- ROW_NUMBER():每行唯一名次,同分也插队(1,2,3)
- GROUP BY 年、月、姓名:先把每个人每个月的销售金额汇总;
- PARTITION BY 年,月:每个月份单独开榜排名,互不干扰;
- ORDER BY SUM(金额) DESC:当月销售额越高,排名越靠前;
6.Linux的一些基本命令
- 日常用:ls、cd、pwd 增删改查文件;
- 看日志用 tail -f、grep 过滤关键字;
- 查 Java 进程 ps -ef、端口 netstat;
- 打包解压 tar,改权限 chmod,杀进程 kill
- netstat -tulpn | grep 8080 看 8080 端口占用
- nohup java -jar app.jar 启服务
7.前后端交互怎么保证它的安全性?
- 登录用 JWT Token 无状态鉴权,密码加密存库;
- 全程 HTTPS 加密传输防抓包;
- 接口用预编译防 SQL 注入,做转义过滤防 XSS、加 CSRF Token 防跨站伪造;
- 核心接口加签名验签、时间戳防篡改和重放;
- 再加 IP 限流防刷、后端全量参数校验、权限控制和敏感数据脱敏,保证交互安全。
不用 Session,用 Token 无状态鉴权
- 登录成功下发:JWT / Token,存 Header(Authorization: Bearer xxx)
- 每次请求必带 Token,后端拦截器统一校验:是否过期、是否篡改、是否合法
- Token 设置合理过期时间,配合刷新 Token续期,避免长期有效被盗
- 密码加密存储 + 传输加密
前端:密码先 RSA/MD5 加盐加密再传
后端:密码BCrypt/SHA256 加盐哈希存库,严禁明文存密码 - 严禁接口明文传密码
8.Redis常用数据类型,什么情况会用这些数据类型?
- String:存 token、验证码、计数器;
- Hash:存用户、商品等对象;
- List:做消息队列、时间线;
- Set:去重、点赞、共同好友;
- ZSet:做排行榜、延时任务;
- Bitmap:签到、状态标记;
- HyperLogLog:UV 统计。
面试
自我介绍然后开始问问题
1.前端用的是什么框架
主要用 Vue2 + Element UI,做后台管理页面和业务交互
2.你们这么多个库,每个库你要指定你是怎么去匹配的
我在项目里通过
注解 + AOP + 配置文件路由来实现数据源切换:
- yml 配置多套数据源,给每个库起名字(比如:master、slave、log、gold);
- 自定义
@DataSource 注解,标注在 Service 或 Mapper 上,指定使用哪个库;- 通过AOP 切面拦截方法,获取注解值,动态切换数据源;
- MyBatis 层通过SqlSessionTemplate绑定对应数据源。 不同业务模块自动匹配对应库,实现多库隔离、互不影响。
微服务常用注解:
3.还有数据源,多种数据源是怎么调配的
我们项目是微服务架构,服务和库是一对一绑定的: 产品服务连产品库,交易服务连交易库,互不跨库访问。 如果一个业务需要多个库的数据,我们通过
Feign 远程调用 获取,不直接跨库连接。 多数据源切换使用 dynamic-datasource, 通过 @DS 注解 指定当前业务使用哪个库,系统自动切换。 交易数据量大,拆了 16 个分库, 使用 ShardingSphere,根据
userId 取模 自动路由分片, 业务代码无感知。
4. 数据库资源占用过多了,怎么去通过SQL语句去关掉它?
数据库资源占用过高时, 先用 show processlist 查看当前所有连接,找到执行时间长、消耗高的 SQL 对应的线程
ID; 然后使用 kill + 线程ID 直接终止该连接,释放资源。 如果是 PostgreSQL,就用
pg_stat_activity 查询,再用 pg_terminate_backend(pid) 杀掉。
show processlist
- id:线程 ID
- user:用户
- Host:地址
- db:库名
- Command:Sleep / Query / Connect
- Time:执行了多久
- State:状态
- Info:正在执行的 SQL
5.服务部署怎么部署的,起服务用来什么命令
- 开发把代码打包成 jar 包
- 上传到服务器对应目录(一般用 rz 或 Jenkins 自动部署)
- 先停止旧服务,防止端口冲突
- 备份旧 jar(避免新版本有问题要回滚)
- 使用 nohup 后台启动命令 启动新服务
- 查看日志是否正常启动
- 检查端口是否监听成功
6.了解过其他数据采集工具吗
- Flume:主要用于日志采集,适合高并发日志实时收集;
- Sqoop:传统关系库之间、关系库与 Hadoop 之间的数据同步;
- DataX:阿里开源的异构数据源同步工具,支持 MySQL、PG、Redis 等各种库之间离线同步,稳定性强,我们对账、批处理同步场景很适合;
- Canal:基于 MySQL binlog 日志的增量数据采集,实现准实时数据同步;
- Flink CDC / Debezium:基于 CDC 的实时数据同步,支持增量采集,适合交易流水这类需要实时同步的场景。
更多推荐


所有评论(0)