Skip to content

专业技能面试准备:问题与回答 ​

基于专业技能版本的完整面试准备指南
每个技术点的问题、标准回答、深入追问


📋 专业技能版本回顾 ​

【核心技能】(熟练掌握,有实际项目经验)

• Java 后端开发
  - 深入理解 Java 基础、集合框架、多线程并发、JVM 内存模型
  - 熟练使用 Spring / Spring Boot(IoC、AOP、事务、常用注解与工程化实践)
  - 熟练使用 MyBatis 进行数据持久化开发

• 数据库与缓存
  - 精通 MySQL:索引优化、事务机制、SQL 调优,有慢查询优化实战经验
  - 熟练使用 Redis:数据类型、持久化机制、分布式锁(对常见缓存问题有理解,但不强调线上实战)

• 微服务组件
  - 熟练使用 Nacos(服务注册发现、配置中心)
  - 熟练使用 Feign(服务间调用、负载均衡)
  - 熟练使用 Xxl-job(分布式任务调度)

• 前端开发
  - 熟练使用 Vue、TypeScript,能独立完成前后端分离项目开发
  - 熟悉 HTML/CSS/JavaScript 基础

• 部署运维
  - 熟练使用 Docker 容器化部署、Linux 环境运维
  - 熟练使用 Maven/Gradle 构建工具、Git 版本控制

【了解技能】(有使用经验,但不够深入)
• 消息队列:了解 RabbitMQ 的基本使用
• 搜索引擎:了解 ElasticSearch 的基本使用

一、Java 后端开发 ​

1.1 Java 基础 ​

❓ 问题1:Java 的三大特性是什么?能详细说说吗? ​

标准回答:

Java 的三大特性是:封装、继承、多态。

封装:将数据和方法包装在类中,隐藏内部实现细节,只暴露必要的接口。比如用 private 修饰字段,提供 getter/setter 方法。

继承:子类可以继承父类的属性和方法,实现代码复用。Java 是单继承,一个类只能继承一个父类。

多态:同一个方法在不同对象上有不同的实现。有两种形式:

  • 编译时多态:方法重载(Overload),同一个类中方法名相同但参数不同
  • 运行时多态:方法重写(Override),子类重写父类方法,通过父类引用调用子类实现

深入追问可能问:

  • "多态的实现原理是什么?" → 答:通过方法表(Method Table)实现,JVM 在运行时根据对象的实际类型调用对应的方法
  • "重载和重写的区别?" → 答:重载是编译时确定,重写是运行时确定;重载方法签名不同,重写方法签名相同

❓ 问题2:HashMap 和 Hashtable 的区别? ​

标准回答:

主要区别有:

  1. 线程安全:Hashtable 是线程安全的(方法用 synchronized 修饰),HashMap 不是线程安全的
  2. 性能:HashMap 性能更好,因为不需要加锁
  3. null 值:HashMap 允许 key 和 value 为 null,Hashtable 不允许
  4. 迭代器:HashMap 的迭代器是 fail-fast,Hashtable 不是
  5. 初始容量:HashMap 默认 16,Hashtable 默认 11

现在基本都用 HashMap,如果需要线程安全,用 ConcurrentHashMap。

深入追问可能问:

  • "HashMap 的底层实现原理?" → 答:JDK1.8 之前是数组+链表,JDK1.8 之后是数组+链表+红黑树。当链表长度超过 8 且数组长度超过 64 时,链表转为红黑树
  • "HashMap 的扩容机制?" → 答:当元素个数超过容量×负载因子(默认 0.75)时,扩容为原来的 2 倍,重新计算 hash 值
  • "为什么用红黑树不用 AVL 树?" → 答:红黑树插入删除性能更好,虽然查询稍慢,但综合性能更好

❓ 问题3:synchronized 和 volatile 的区别? ​

标准回答:

synchronized:

  • 保证原子性和可见性
  • 可以修饰方法或代码块
  • 是重量级锁,性能开销大
  • 可以保证互斥,同一时间只有一个线程能执行

volatile:

  • 只保证可见性和有序性,不保证原子性
  • 只能修饰变量
  • 性能开销小
  • 不能保证互斥,多个线程可以同时修改变量

使用场景:

  • synchronized:需要保证原子性的场景,比如计数器、共享资源访问
  • volatile:只需要保证可见性的场景,比如标志位、单例模式的双重检查

深入追问可能问:

  • "volatile 的实现原理?" → 答:通过内存屏障(Memory Barrier)实现,保证写操作立即刷新到主内存,读操作从主内存读取
  • "synchronized 的锁升级过程?" → 答:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

❓ 问题4:ArrayList 和 LinkedList 的区别?怎么选择? ​

标准回答:

ArrayList:

  • 底层是数组,支持随机访问
  • 查询快(O(1)),插入删除慢(O(n)),因为需要移动元素
  • 适合查询多、插入删除少的场景

LinkedList:

  • 底层是双向链表
  • 查询慢(O(n)),插入删除快(O(1)),只需要修改指针
  • 适合插入删除多、查询少的场景

选择建议:

  • 大部分场景用 ArrayList,因为随机访问性能好
  • 只有在频繁插入删除中间元素时才考虑 LinkedList

深入追问可能问:

  • "ArrayList 的扩容机制?" → 答:默认初始容量 10,扩容时容量变为原来的 1.5 倍,使用 Arrays.copyOf() 复制数组

❓ 问题5:什么是 Java 内存模型(JMM)? ​

标准回答:

Java 内存模型(JMM)定义了 Java 程序中变量的访问规则,解决了多线程环境下的可见性、有序性问题。

主要概念:

  • 主内存:所有线程共享的内存区域
  • 工作内存:每个线程私有的内存区域,存储主内存中变量的副本
  • 内存屏障:保证指令执行的顺序性和可见性

volatile 的作用:

  • 保证可见性:写操作立即刷新到主内存,读操作从主内存读取
  • 保证有序性:禁止指令重排序

深入追问可能问:

  • "JMM 和 JVM 内存结构的区别?" → 答:JMM 是抽象概念,定义了多线程下的内存访问规则;JVM 内存结构是具体的实现,包括堆、栈、方法区等

1.1+ 多线程与并发(加分题,但小公司也常问) ​

❓ 问题6:线程的生命周期?sleep() / wait() / join() 区别? ​

标准回答:

线程状态(常见):NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED。

  • sleep():让当前线程进入 TIMED_WAITING,不释放锁。
  • wait():让当前线程进入 WAITING/TIMED_WAITING,会释放锁,必须在 synchronized 内调用,靠 notify/notifyAll 唤醒。
  • join():让当前线程等待目标线程执行完(本质是对目标线程对象做 wait())。

深入追问可能问:

  • "为什么 wait() 必须在同步块里?" → 答:因为要释放/重新竞争同一把监视器锁,必须持有 monitor 才能操作。

❓ 问题7:synchronized 和 ReentrantLock 区别? ​

标准回答:

synchronized 是 JVM 层面的内置锁,语法简单;ReentrantLock 是 JUC 显式锁,功能更丰富。
主要区别:

  • 是否可中断:Lock 支持 lockInterruptibly();synchronized 不支持。
  • 公平锁:Lock 可配置公平/非公平;synchronized 只有非公平。
  • 条件队列:Lock 支持多个 Condition;synchronized 只有一个 wait/notify 队列。
  • 释放锁方式:Lock 需要 try/finally 手动释放;synchronized 自动释放。

深入追问可能问:

  • "什么是可重入?" → 答:同一线程拿到锁后再次请求同一把锁不会死锁,计数+1,退出时计数-1。

❓ 问题8:线程池为什么要用?核心参数有哪些?怎么配? ​

标准回答:

为什么用线程池:复用线程,降低创建/销毁开销;控制并发度;统一管理任务队列与拒绝策略。
核心参数(ThreadPoolExecutor):corePoolSize、maximumPoolSize、keepAliveTime、workQueue、threadFactory、handler。
怎么配(思路):

  • IO 密集:线程数可略大(比如 2×CPU 左右,结合压测调整)。
  • CPU 密集:线程数接近 CPU 核数(避免上下文切换)。
  • 队列大小和拒绝策略要结合业务:是否允许丢弃、是否允许同步回退。

深入追问可能问:

  • "常见拒绝策略?" → 答:Abort(抛异常)、CallerRuns(调用线程执行)、Discard(丢弃)、DiscardOldest(丢弃最老)。

❓ 问题9:什么是死锁?怎么排查?怎么避免? ​

标准回答:

死锁:多个线程互相持有对方需要的锁,互相等待无法继续。
排查:

  • 线上:jstack pid 看线程堆栈与死锁提示;也可以配合监控定位阻塞点。
    避免:
  • 统一加锁顺序;
  • 减少锁粒度/锁时间;
  • 尝试 tryLock + 超时;
  • 必要时使用无锁/并发容器。

1.1++ JVM(常问:内存/GC/排查) ​

❓ 问题10:JVM 内存结构是怎样的?哪些线程私有? ​

标准回答:

常见内存结构:

  • 堆(Heap):对象实例,GC 主要区域,线程共享。
  • 方法区/元空间(Metaspace):类元数据、常量池等,线程共享。
  • 虚拟机栈(VM Stack):栈帧(局部变量表/操作数栈等),线程私有。
  • 本地方法栈:JNI 调用相关,线程私有。
  • 程序计数器:当前线程执行到哪条字节码指令,线程私有。

深入追问可能问:

  • "为什么程序计数器是私有的?" → 答:线程切换后需要恢复到正确执行位置。

❓ 问题11:什么情况会 OOM?你怎么定位? ​

标准回答:

常见 OOM:

  • Java heap space:堆内存不足(对象太多/内存泄漏)。
  • GC overhead limit exceeded:GC 频繁但回收很少。
  • Metaspace:类加载过多(动态代理/反射/频繁生成类)。
  • unable to create new native thread:线程太多/系统资源不足。
    定位思路:
  • 打开堆转储:-XX:+HeapDumpOnOutOfMemoryError;
  • 用 MAT/VisualVM 分析大对象、引用链;
  • 结合 GC 日志看回收与晋升情况。

❓ 问题12:GC 常见算法与回收器你了解哪些?怎么选? ​

标准回答:

算法:标记-清除、标记-整理、复制(新生代常用)。
回收器(了解即可):\n> - 关注点一般是:吞吐(Throughput) vs 延迟(Pause)。
实际选择更多看 JDK 版本与线上指标:先用默认回收器,配合 GC 日志与压测调参。


1.2 Spring Boot ​

❓ 问题1:Spring Boot 的核心特性是什么? ​

标准回答:

Spring Boot 的核心特性:

  1. 自动配置(Auto Configuration):根据 classpath 中的 jar 包自动配置 Spring 应用
  2. 起步依赖(Starter):提供预配置的依赖组合,简化依赖管理
  3. 内嵌服务器:内置 Tomcat、Jetty 等服务器,无需部署 WAR 包
  4. 生产就绪:提供监控、健康检查等功能

优势:简化配置,快速开发,约定优于配置

深入追问可能问:

  • "自动配置的原理?" → 答:通过 @EnableAutoConfiguration 注解,扫描 META-INF/spring.factories 文件,加载自动配置类
  • "如何自定义自动配置?" → 答:创建配置类,用 @Configuration 和 @ConditionalOnClass 等条件注解,在 spring.factories 中注册

❓ 问题2:Spring Boot 的启动流程? ​

标准回答:

Spring Boot 的启动流程:

  1. 创建 SpringApplication 对象:初始化应用上下文
  2. 运行 run 方法:
    • 创建并配置 Environment(环境变量、配置文件)
    • 创建 ApplicationContext(应用上下文)
    • 刷新上下文:加载 Bean、执行后置处理器
    • 执行 CommandLineRunner 和 ApplicationRunner
  3. 启动内嵌服务器:Tomcat、Jetty 等

深入追问可能问:

  • "Bean 的加载过程?" → 答:扫描 → 解析 → 注册 → 实例化 → 初始化 → 使用

1.3 MyBatis ​

❓ 问题1:MyBatis 的执行流程? ​

标准回答:

MyBatis 的执行流程:

  1. 加载配置:读取 mybatis-config.xml 和 Mapper 文件
  2. 创建 SqlSessionFactory:通过 SqlSessionFactoryBuilder 构建
  3. 创建 SqlSession:通过 SqlSessionFactory 创建
  4. 执行 SQL:
    • 获取 Mapper 接口的代理对象
    • 执行 SQL:解析 SQL、设置参数、执行查询、结果映射
  5. 关闭 SqlSession

深入追问可能问:

  • "#{} 和 ${} 的区别?" → 答:#{} 是预编译,防止 SQL 注入;${} 是字符串替换,有 SQL 注入风险
  • "MyBatis 的一级缓存和二级缓存?" → 答:一级缓存是 SqlSession 级别的,二级缓存是 Mapper 级别的

❓ 问题2:Spring 的 IoC 和 AOP 是什么? ​

标准回答:

IoC(控制反转):

  • 将对象的创建和依赖关系的管理交给 Spring 容器
  • 通过依赖注入(DI)实现,不需要手动 new 对象
  • 降低代码耦合度,提高可维护性

AOP(面向切面编程):

  • 将横切关注点(日志、事务、权限等)从业务逻辑中分离
  • 通过代理模式实现,在不修改原有代码的情况下增强功能
  • 常见应用:事务管理、日志记录、性能监控

实战经验: 在项目中,IoC 主要用于依赖注入,比如 Service 注入 DAO,Controller 注入 Service。AOP 主要用于事务管理,使用 @Transactional 注解管理事务。

深入追问可能问:

  • "IoC 的实现原理?" → 答:通过反射创建对象,通过依赖注入设置属性,通过 Bean 容器管理对象生命周期
  • "AOP 的代理模式?" → 答:JDK 动态代理(基于接口)和 CGLIB 代理(基于继承),Spring 默认使用 JDK 动态代理,如果没有接口则使用 CGLIB
  • "@Transactional 的原理?" → 答:通过 AOP 代理,在方法执行前开启事务,执行后提交或回滚事务

❓ 问题3:Spring 的事务管理? ​

标准回答:

事务的传播行为:

  • REQUIRED(默认):如果当前有事务,加入事务;如果没有,创建新事务
  • REQUIRES_NEW:总是创建新事务,挂起当前事务
  • SUPPORTS:如果当前有事务,加入事务;如果没有,以非事务方式执行
  • NOT_SUPPORTED:以非事务方式执行,挂起当前事务
  • MANDATORY:必须在事务中执行,否则抛出异常
  • NEVER:不能在事务中执行,否则抛出异常
  • NESTED:如果当前有事务,创建嵌套事务;如果没有,创建新事务

事务的隔离级别:

  • DEFAULT:使用数据库默认隔离级别
  • READ_UNCOMMITTED:读未提交
  • READ_COMMITTED:读已提交
  • REPEATABLE_READ:可重复读
  • SERIALIZABLE:串行化

实战经验: 在项目中,大部分场景使用默认的 REQUIRED 传播行为。对于需要独立事务的场景(如记录日志),使用 REQUIRES_NEW。

深入追问可能问:

  • "事务失效的场景?" → 答:方法不是 public、异常被捕获、同一个类内方法调用(没有通过代理)、数据库不支持事务
  • "如何排查事务问题?" → 答:开启事务日志,查看事务是否开启、提交、回滚;检查异常是否被捕获

二、数据库与缓存 ​

2.1 MySQL ​

❓ 问题1:MySQL 索引用的什么数据结构?为什么用 B+ 树? ​

标准回答:

MySQL 索引用的是 B+ 树。

为什么不用红黑树?

  • 红黑树是二叉树,树高较高,磁盘 IO 次数多
  • B+ 树是多叉树,树高较低,磁盘 IO 次数少
  • B+ 树叶子节点有序排列,支持范围查询
  • B+ 树叶子节点存储数据,非叶子节点只存储索引,查询效率稳定

为什么不用 B 树?

  • B+ 树非叶子节点不存储数据,可以存储更多索引,树高更低
  • B+ 树叶子节点用链表连接,范围查询更方便

深入追问可能问:

  • "聚集索引和非聚集索引的区别?" → 答:聚集索引的叶子节点存储数据行,非聚集索引的叶子节点存储主键值;InnoDB 用聚集索引,MyISAM 用非聚集索引
  • "联合索引的最左前缀原则?" → 答:联合索引 (a, b, c),查询条件必须包含最左边的列才能使用索引,比如 WHERE a = 1 或 WHERE a = 1 AND b = 2 可以使用索引,但 WHERE b = 2 不能使用索引

❓ 问题2:索引失效的场景有哪些? ​

标准回答:

索引失效的常见场景:

  1. 最左前缀原则失效:联合索引 (a, b, c),查询条件不包含最左边的列
  2. 对索引列做函数或运算:WHERE YEAR(create_time) = 2024、WHERE age + 1 = 20
  3. 隐式类型转换:字符串字段用数字查询,如 WHERE phone = 1234567890(phone 是 varchar)
  4. LIKE 查询以 % 开头:WHERE name LIKE '%张'
  5. OR 条件中混入非索引列:WHERE id = 1 OR name = '张三'(name 不是索引)
  6. 使用 != 或 <>:WHERE status != 1
  7. IS NULL 或 IS NOT NULL:某些情况下会失效

深入追问可能问:

  • "你是怎么排查索引失效的?" → 答:使用 EXPLAIN 查看执行计划,关注 type 字段(ALL 表示全表扫描)、key 字段(NULL 表示未使用索引)
  • "能举个例子吗?" → 答:在项目中遇到过联合索引 (user_id, create_time),查询 WHERE create_time > '2024-01-01' 时索引失效,改为 WHERE user_id = 1 AND create_time > '2024-01-01' 后索引生效

❓ 问题3:MySQL 事务的隔离级别?MVCC 原理? ​

标准回答:

MySQL 的 4 种隔离级别:

  1. READ UNCOMMITTED(读未提交):可能脏读、不可重复读、幻读
  2. READ COMMITTED(读已提交):解决脏读,可能不可重复读、幻读
  3. REPEATABLE READ(可重复读):解决脏读、不可重复读,可能幻读(InnoDB 通过 MVCC 解决大部分幻读)
  4. SERIALIZABLE(串行化):完全隔离,性能最差

MVCC(多版本并发控制)原理:

  • 每行数据有隐藏字段:trx_id(事务ID)、roll_pointer(回滚指针)
  • 通过 undo log 构建版本链,实现快照读
  • ReadView 判断哪些版本对当前事务可见
  • 解决了大部分幻读问题(快照读),但当前读仍可能幻读(需要间隙锁)

深入追问可能问:

  • "快照读和当前读的区别?" → 答:快照读是读取历史版本(SELECT),当前读是读取最新版本(UPDATE、DELETE、SELECT FOR UPDATE)
  • "MVCC 如何解决不可重复读?" → 答:同一事务中多次读取同一数据,都读取的是同一个版本(ReadView 不变)

❓ 问题4:慢查询怎么排查?SQL 优化经验? ​

标准回答:

慢查询排查步骤:

  1. 开启慢查询日志:SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 2;
  2. 查看慢查询日志:找到执行时间超过阈值的 SQL
  3. 使用 EXPLAIN 分析:查看执行计划,关注:
    • type:ALL(全表扫描)需要优化
    • key:使用的索引
    • rows:扫描的行数
    • Extra:Using filesort、Using temporary 需要优化
  4. 优化 SQL:
    • 添加索引
    • 优化查询条件(避免索引失效)
    • 优化 JOIN(小表驱动大表)
    • 优化子查询(改为 JOIN)

实战案例: 在项目中遇到过订单列表查询慢的问题,通过 EXPLAIN 分析发现:

  • 联合索引 (user_id, create_time) 因为查询条件不包含 user_id,索引失效
  • 深分页查询 LIMIT 10000, 20 性能差

优化方案:

  1. 修改查询条件,加上 user_id
  2. 使用延迟关联优化深分页:先查主键,再关联查询

优化效果:查询时间从 5-8 秒降到 200ms 以内

深入追问可能问:

  • "延迟关联的原理?" → 答:先通过索引查询主键(LIMIT 很快),再用主键关联查询数据,避免回表时扫描大量数据
  • "深分页还有其他优化方案吗?" → 答:使用游标分页(记录上一页的最大ID),或者使用搜索引擎(ElasticSearch)

2.2 Redis ​

❓ 问题1:Redis 的数据类型有哪些?各自的应用场景? ​

标准回答:

Redis 有 5 种基础数据类型:

  1. String(字符串):

    • 缓存:缓存用户信息、商品详情(JSON 格式)
    • 计数器:点赞数、阅读量(INCR 命令)
    • 分布式锁:SETNX 命令
  2. Hash(哈希):

    • 对象存储:用户对象(key=user:1, field=name, value=张三)
    • 购物车:key=购物车ID,field=商品ID,value=数量
    • 优势:可以单独修改某个字段,不需要全量更新
  3. List(列表):

    • 消息队列:LPUSH + RPOP
    • 最新列表:朋友圈动态、浏览记录
  4. Set(集合):

    • 去重:独立 IP 访问量(UV)
    • 交并差集:共同好友、标签推荐
  5. ZSet(有序集合):

    • 排行榜:微博热搜、游戏积分排名
    • 延时队列:用分数表示时间

深入追问可能问:

  • "String 和 Hash 怎么选择?" → 答:如果经常需要修改对象的某个字段,用 Hash;如果对象整体更新,用 String(JSON)
  • "List 做消息队列有什么问题?" → 答:不支持消息确认机制,消息可能丢失;一般用专业的消息队列(RabbitMQ、RocketMQ)

❓ 问题2:缓存穿透、击穿、雪崩是什么?怎么解决?(了解即可) ​

标准回答:

缓存穿透:

  • 问题:查询不存在的数据,缓存和数据库都没有,大量请求打到数据库
  • 解决方案:
    1. 布隆过滤器:过滤无效请求
    2. 缓存空值:即使查询不到也缓存,设置较短的过期时间

缓存击穿:

  • 问题:热点 key 过期,大量请求同时打到数据库
  • 解决方案:
    1. 分布式锁:使用 Redisson 实现,只允许一个线程查询数据库
    2. 热点数据永不过期:或者异步更新

缓存雪崩:

  • 问题:大量 key 同时过期,大量请求打到数据库
  • 解决方案:
    1. 设置随机过期时间:避免同时过期
    2. 多级缓存:本地缓存 + Redis 缓存
    3. 限流/降级:必要时做接口限流、降级或兜底返回,保护数据库

回答建议: 这类问题我能讲清楚定义、风险与常见方案;如果面试官希望我结合项目,我会按“我们项目的读写特征/热点 Key/是否允许短暂不一致”来说明我会怎么做(避免夸大自己已经线上处理过)。

深入追问可能问:

  • "布隆过滤器的原理?" → 答:使用多个哈希函数,将元素映射到位数组的多个位置,查询时如果所有位置都是 1,可能存在;如果有 0,一定不存在。缺点:可能误判(存在误报,不存在不误报)
  • "分布式锁的实现原理?" → 答:使用 SETNX 命令设置锁,设置过期时间防止死锁。Redisson 实现了看门狗机制,自动续期

❓ 问题3:Redis 的持久化机制?RDB 和 AOF 的区别? ​

标准回答:

Redis 有两种持久化方式:

RDB(快照):

  • 原理:定期将内存中的数据快照保存到磁盘
  • 优点:文件小,恢复快
  • 缺点:可能丢失最后一次快照后的数据
  • 配置:save 900 1(900 秒内至少 1 个 key 变化)

AOF(追加日志):

  • 原理:记录每个写操作,追加到文件末尾
  • 优点:数据安全,最多丢失 1 秒数据
  • 缺点:文件大,恢复慢
  • 配置:appendonly yes、appendfsync everysec

生产环境建议:

  • 同时开启 RDB 和 AOF
  • RDB 做备份,AOF 保证数据安全

深入追问可能问:

  • "AOF 重写是什么?" → 答:AOF 文件过大时,重写生成新的 AOF 文件,只保留最终状态,减小文件大小
  • "RDB 和 AOF 的恢复顺序?" → 答:如果同时开启,优先使用 AOF 恢复(数据更完整)

❓ 问题4:怎么用 Redis 实现分布式锁? ​

标准回答:

基本实现:

java
// 设置锁,NX 表示不存在才设置,EX 表示过期时间
SET lock_key lock_value NX EX 30

// 释放锁,需要判断 value 是否匹配,防止误删
if (get("lock_key") == lock_value) {
    del("lock_key");
}

问题:

  1. 锁过期但业务未执行完:需要看门狗机制自动续期
  2. 释放锁时误删:需要 value 唯一标识

Redisson 实现:

  • 使用 Lua 脚本保证原子性
  • 看门狗机制:后台线程定期续期
  • 可重入锁:支持同一线程多次加锁

实战案例: 在项目中用 Redis 分布式锁防止重复提交。用户提交订单时,先获取锁(key=order:user_id,过期时间 10 秒),如果获取成功则处理订单,处理完释放锁。如果获取失败,说明正在处理,返回"请勿重复提交"。

深入追问可能问:

  • "Redisson 的看门狗机制?" → 答:后台线程每 10 秒检查一次,如果锁还在且是当前线程持有,则续期 30 秒
  • "如果 Redis 主从切换,锁丢失怎么办?" → 答:使用 Redlock 算法,在多个 Redis 节点上同时加锁,只有大多数节点成功才算加锁成功

三、前端开发 ​

4.1 Vue ​

❓ 问题1:前后端分离是怎么做的? ​

标准回答:

前后端分离架构:

  • 前端:Vue 单页应用(SPA),独立部署
  • 后端:RESTful API,提供数据接口
  • 通信:HTTP/HTTPS,JSON 格式

开发流程:

  1. 前后端约定接口规范(请求参数、响应格式)
  2. 前端开发页面,使用 Mock 数据
  3. 后端开发接口
  4. 联调测试

优势:

  • 前后端可以并行开发
  • 前端可以独立部署,不影响后端
  • 技术栈解耦,前端可以用任何框架

深入追问可能问:

  • "跨域问题怎么解决?" → 答:后端配置 CORS(Cross-Origin Resource Sharing),允许前端域名访问;或者使用 Nginx 反向代理
  • "Token 认证是怎么实现的?" → 答:用户登录后,后端返回 Token,前端存储 Token(localStorage),每次请求在 Header 中携带 Token,后端验证 Token

❓ 问题2:Vue 的核心特性? ​

标准回答:

Vue 的核心特性:

  1. 响应式数据:数据变化自动更新视图
  2. 组件化:可复用的组件
  3. 虚拟 DOM:提高渲染性能
  4. 指令系统:v-if、v-for、v-bind 等
  5. 生命周期钩子:created、mounted、updated 等

深入追问可能问:

  • "Vue 的响应式原理?" → 答:使用 Object.defineProperty(Vue 2)或 Proxy(Vue 3)劫持数据,数据变化时触发视图更新
  • "Vuex 的作用?" → 答:状态管理,管理全局共享状态

四、部署运维 ​

5.1 Docker ​

❓ 问题1:Docker 和虚拟机的区别? ​

标准回答:

Docker:

  • 容器化技术,共享宿主机内核
  • 启动快(秒级)
  • 资源占用少
  • 隔离性较弱

虚拟机:

  • 虚拟化技术,每个虚拟机有独立内核
  • 启动慢(分钟级)
  • 资源占用多
  • 隔离性强

使用场景:

  • Docker:应用部署、微服务、CI/CD
  • 虚拟机:需要完全隔离的场景

深入追问可能问:

  • "Dockerfile 的常用指令?" → 答:FROM(基础镜像)、RUN(执行命令)、COPY(复制文件)、WORKDIR(工作目录)、EXPOSE(暴露端口)、CMD(启动命令)
  • "多阶段构建是什么?" → 答:在 Dockerfile 中使用多个 FROM,前一个阶段用于构建,后一个阶段用于运行,减小镜像大小

❓ 问题2:Docker 的网络模式? ​

标准回答:

Docker 的网络模式:

  1. bridge(桥接):默认模式,容器通过虚拟网桥通信
  2. host(主机):容器直接使用宿主机网络
  3. none(无):容器没有网络
  4. container(容器):共享其他容器的网络

常用场景:

  • bridge:大多数场景
  • host:需要高性能的场景

深入追问可能问:

  • "Docker Compose 的作用?" → 答:定义多容器应用,一键启动多个容器,管理容器之间的依赖关系

5.2 Linux ​

❓ 问题1:Linux 常用命令? ​

标准回答:

文件操作:

  • ls:列出文件
  • cd:切换目录
  • mkdir:创建目录
  • rm:删除文件
  • cp:复制文件
  • mv:移动文件

查看文件:

  • cat:查看文件内容
  • tail -f:实时查看日志
  • grep:搜索文本

进程管理:

  • ps:查看进程
  • kill:杀死进程
  • top:查看系统资源

网络:

  • netstat:查看网络连接
  • curl:发送 HTTP 请求

深入追问可能问:

  • "怎么排查服务器问题?" → 答:查看日志(tail -f)、查看进程(ps、top)、查看网络(netstat)、查看磁盘(df -h)

五、了解技能(简单回答) ​

6.1 RabbitMQ ​

❓ 问题:RabbitMQ 的基本使用? ​

标准回答:

我在项目中了解过 RabbitMQ,主要用于异步消息处理。

基本概念:

  • 生产者:发送消息
  • 消费者:接收消息
  • 队列:存储消息
  • 交换机:路由消息

使用场景:

  • 异步处理:下单后异步发送短信
  • 解耦:服务之间通过消息队列解耦

注意:我在项目中主要是了解基本使用,深入原理还在学习中。


6.2 ElasticSearch ​

❓ 问题:ElasticSearch 的基本使用? ​

标准回答:

我在项目中了解过 ElasticSearch,主要用于全文搜索。

基本概念:

  • 索引(Index):类似数据库
  • 文档(Document):类似表中的行
  • 字段(Field):类似表中的列

使用场景:

  • 商品搜索:根据关键词搜索商品
  • 日志分析:分析日志数据

注意:我在项目中主要是了解基本使用,深入原理还在学习中。


六、物联网项目高频面试题(结合你当前远程项目) ​

针对你简历中的「物联网设备平台 / 智能设备项目」,整理一些高频、且你真正在做或能讲清楚的问题。

6.1 物联网项目整体架构 ​

❓ 问题1:简单介绍下你做的物联网项目整体架构? ​

标准回答(按自己项目微调):

我最近做的物联网项目主要是针对某类设备做远程监控和管理,整体可以简单拆成四层:
1)设备端:设备周期性上报运行状态(比如容量、温度、电量等),部分通过 HTTP/小程序接口,部分通过 TCP/Netty 网关转发。
2)接入层服务:由 Java + Spring Boot 提供统一的设备接入接口,做设备鉴权、参数校验、数据格式转换,并把数据写入数据库/缓存。
3)业务服务层:负责设备管理(注册、分组、绑定)、数据查询、告警规则、统计报表等。
4)展示层:包括 Web 管理端(Vue)和小程序后台,用来查看设备状态、历史曲线、处理告警等。
整体上就是:设备 → 接入服务 → 数据库/缓存 → Web/小程序管理端。

可能的深入追问:

  • “设备是怎么接入的?HTTP 还是 MQTT?” → 按你真实情况回答:现在项目以 HTTP/Netty 为主,可以说「MQTT 了解概念,当前项目主要是 HTTP/TCP 接入」。

6.2 设备接入与在线状态 ​

❓ 问题2:设备如何注册和鉴权?怎么防止伪造数据? ​

标准回答:

我们对设备接入做了简单的注册和鉴权:

  • 首先在后台为每台设备生成 deviceId 和一个密钥(或绑定 SN 号),录入数据库。
  • 设备首次接入时,使用 deviceId + 签名/密钥 调用注册/绑定接口,服务端校验通过后标记状态为“已激活”。
  • 后续数据上报接口也会带上 deviceId 和签名(或 token),服务端会做一次校验,防止伪造请求。
    同时在网关/接口层会做 IP 白名单、频率限制等基础防护。

可能的深入追问:

  • “签名大致怎么做的?” → 可以说「按约定字段 + 密钥做一次哈希/摘要校验」即可,不用细到具体算法。

❓ 问题3:你们是怎么判断设备在线/离线的? ​

标准回答:

在线/离线主要是通过心跳 + 最近上报时间判断:

  • 设备会定期发送心跳或数据上报,比如每 1 分钟或每 5 分钟一次。
  • 后端收到后,会在 Redis 里记录一条 device:online:{deviceId},value 存最后一次上报时间,并设置一个过期时间。
  • 页面查询设备列表时,如果 Redis 中 key 存在且时间在允许范围内,就认为在线,否则视为离线。
    这样做的好处是读写都很轻量,列表页面可以很快看到在线状态。

可能的深入追问:

  • “Redis 里的 key 设计和过期时间怎么定?” → 可以回答「key 里带 deviceId,过期时间略大于心跳间隔两三倍,比如心跳 1 分钟一次就设置 3~5 分钟」。

6.3 数据存储与查询 ​

❓ 问题4:设备上报的数据是怎么落库的?表结构怎么设计? ​

标准回答:

设备上报的数据我这边是按结构化表 + 时间序列字段来存的,大概是:

  • 一张设备表:存设备基础信息(id、device_code、name、type、location、status...)。
  • 一张或多张数据表:比如 device_telemetry,字段包括 id、device_id、metric、value、unit、report_time。
  • 核心索引一般是 (device_id, report_time) 的联合索引,方便按设备 + 时间范围查询最近的数据或历史曲线。
    如果数据量太大,会考虑做归档或按时间分表,但我现在项目规模暂时还用不到特别复杂的时序方案。

可能的深入追问:

  • “深查询/最近 N 条怎么查?” → 可以回答「按 deviceId + 时间倒排,配合 limit 做分页和最近 N 条查询」。

❓ 问题5:前端页面是怎么展示设备历史数据和实时状态的? ​

标准回答:

前端主要有两个常见页面:
1)设备列表页:调用后端接口拿设备基础信息 + 在线状态(Redis 缓存),做分页展示。
2)设备详情/监控页:

  • 请求历史数据接口:按 deviceId + 时间范围 查出一批点位数据,前端用折线图/柱状图展示。
  • 实时状态可以通过轮询接口或者 WebSocket 推送最新一条数据。
    这样面试官也能看到你对“接口 + 数据结构 + 前端展示”的整体链路是清楚的。

6.4 告警规则与处理流程 ​

❓ 问题6:告警是怎么做的?比如「垃圾桶满载」这种场景? ​

标准回答:

告警部分我这边是按一个比较简单、但好落地的方案来实现的:

  • 在设备数据表中,某些指标(比如容量百分比、电量、电压等)是我们关心的监控字段。
  • 后端在数据上报时或通过定时任务,对这些字段做规则判断,比如“容量 > 90% 且连续 N 次上报都是高值”就认为满载。
  • 满足条件时,写一条告警记录到 device_alarm 表,并在管理端/小程序上展示未处理告警。
  • 告警处理后可以记录处理人、处理时间和备注。
    这种规则简单直观,比较适合中小项目,后面如果要做更复杂的规则引擎也有扩展空间。

可能的深入追问:

  • “避免抖动/误报你们有什么处理?” → 可以回答「通过连续多次超过阈值、设定恢复阈值,避免单次波动就触发告警」。

6.5 物联网项目和普通业务系统有什么不同? ​

❓ 问题7:你觉得物联网项目和普通 CRM/ERP 这类系统,最大的区别/难点在哪? ​

标准回答:

我这边的体会是主要有三点:
1)数据来源不同:物联网项目的数据是设备实时上报的,具有连续性和时间序列特征,需要考虑丢包、延迟、重复上报等问题;普通业务更多是用户操作驱动。
2)在线状态和告警:物联网要非常关注设备在线/离线、异常告警,而普通业务系统更多关注订单/流程状态。
3)前后端联动:物联网监控页面会有大量图表、地图、实时刷新,对接口性能和前端展示有更高要求。
但技术栈上还是以 Spring Boot + MySQL + Redis + 前端框架为主,只是在“设备接入、心跳、数据模型和告警逻辑”上多了一层领域特点。

可能的深入追问:

  • “你在物联网项目里具体负责了哪些模块?” → 这里就按你真实做过的:设备管理、数据查询接口、告警模块、Web 管理端、小程序后台等逐一说清楚即可。

七、项目常见问题与解决方案 ​

7.1 性能优化问题 ​

❓ 问题1:项目中遇到过哪些性能问题?怎么解决的? ​

标准回答:

数据库性能问题:

  • 问题:订单列表查询慢,响应时间 5-8 秒
  • 排查:使用慢查询日志和 EXPLAIN 分析,发现索引失效
  • 解决:优化查询条件,添加联合索引,使用延迟关联优化深分页
  • 效果:查询时间降到 200ms 以内

接口性能问题:

  • 问题:商品详情页接口响应慢,高并发时数据库压力大
  • 解决:使用 Redis 缓存商品信息,减少数据库查询
  • 效果:接口响应时间从 200ms 降到 15ms,数据库压力降低 80%

前端性能问题:

  • 问题:页面加载慢,首屏渲染时间长
  • 解决:使用 Vue 路由懒加载、图片懒加载、代码分割
  • 效果:首屏加载时间减少 50%

深入追问可能问:

  • "你是怎么定位性能问题的?" → 答:使用慢查询日志、APM 工具(如 SkyWalking)、浏览器性能分析工具
  • "性能优化的优先级?" → 答:先优化数据库(影响最大),再优化缓存,最后优化前端

❓ 问题2:如何排查线上问题? ​

标准回答:

排查步骤:

  1. 查看日志:使用 tail -f 查看应用日志,定位错误信息
  2. 查看监控:检查 CPU、内存、磁盘、网络使用情况
  3. 查看数据库:检查慢查询日志,分析 SQL 执行情况
  4. 查看中间件:检查 Redis、消息队列等中间件状态
  5. 复现问题:在测试环境复现问题,定位根本原因

常用工具:

  • 日志:tail -f、grep、less
  • 监控:top、htop、iostat
  • 网络:netstat、tcpdump
  • 数据库:EXPLAIN、慢查询日志

实战案例: 在项目中遇到过接口超时问题,通过查看日志发现是数据库查询慢,使用 EXPLAIN 分析发现索引失效,优化后问题解决。

深入追问可能问:

  • "如何快速定位线上问题?" → 答:先看错误日志,再看监控指标,最后看业务逻辑
  • "如何避免线上问题?" → 答:代码审查、单元测试、压力测试、灰度发布

7.2 部署与运维问题 ​

❓ 问题1:Docker 部署遇到过什么问题? ​

标准回答:

常见问题:

  1. 容器启动失败:

    • 原因:端口冲突、资源不足、镜像问题
    • 解决:检查端口占用、调整资源限制、重新构建镜像
  2. 容器无法访问数据库:

    • 原因:网络配置问题、防火墙限制
    • 解决:使用 Docker 网络、配置防火墙规则
  3. 容器日志过大:

    • 原因:日志没有轮转,占用磁盘空间
    • 解决:配置日志轮转、使用日志收集工具(如 ELK)

实战经验: 在项目中,使用 Docker Compose 编排多个容器,配置了日志轮转和健康检查,保证服务稳定运行。

深入追问可能问:

  • "Docker 镜像优化?" → 答:使用多阶段构建、减少镜像层数、使用 .dockerignore 排除无用文件
  • "如何实现容器高可用?" → 答:使用 Docker Swarm 或 Kubernetes,配置健康检查和自动重启

❓ 问题2:Linux 运维常见问题? ​

标准回答:

常见问题:

  1. 磁盘空间不足:

    • 排查:使用 df -h 查看磁盘使用情况
    • 解决:清理日志文件、删除无用文件、扩容磁盘
  2. 内存不足:

    • 排查:使用 free -h 查看内存使用情况
    • 解决:调整应用内存配置、增加 swap 空间、优化代码
  3. 进程占用 CPU 高:

    • 排查:使用 top 查看进程 CPU 使用情况
    • 解决:优化代码、调整线程池大小、限流

实战经验: 在项目中,定期清理日志文件,配置了日志轮转,避免磁盘空间不足。使用监控工具(如 Prometheus)监控服务器资源。

深入追问可能问:

  • "如何排查服务器性能问题?" → 答:使用 top、iostat、netstat 等工具,分析 CPU、内存、磁盘、网络使用情况
  • "如何保证服务高可用?" → 答:使用负载均衡、多实例部署、健康检查、自动故障转移

7.3 团队协作与项目管理 ​

❓ 问题1:远程工作如何保证沟通和协作? ​

标准回答:

沟通方式:

  • 即时沟通:使用企业微信、钉钉、Slack 等工具
  • 异步沟通:使用邮件、文档、任务管理系统
  • 定期会议:每日站会、周会、项目评审会

协作工具:

  • 代码管理:Git、GitLab、GitHub
  • 项目管理:Jira、Trello、Teambition
  • 文档协作:Confluence、语雀、Notion

协作习惯:

  • 及时回复消息,主动反馈进度
  • 代码提交前进行代码审查
  • 遇到问题及时沟通,不拖延
  • 按时交付,保证代码质量

实战经验: 在远程工作中,我每天固定时间汇报进度,遇到问题及时沟通,使用 Git 进行代码管理,使用任务管理系统跟踪任务进度。

深入追问可能问:

  • "如何保证远程工作效率?" → 答:制定工作计划、使用时间管理工具、保持专注、定期总结
  • "如何处理远程工作的时差问题?" → 答:明确工作时间、使用异步沟通、重要事项提前通知

❓ 问题2:如何管理项目进度? ​

标准回答:

项目管理方法:

  1. 任务拆分:将大任务拆分成小任务,明确优先级
  2. 时间估算:根据历史经验估算任务时间,留出缓冲时间
  3. 进度跟踪:使用任务管理系统跟踪任务进度,及时调整
  4. 风险管控:识别项目风险,提前准备应对方案

实战经验: 在项目中,我使用任务管理系统(如 Jira)管理任务,每天更新任务状态,每周总结项目进度,遇到风险及时上报。

深入追问可能问:

  • "如何应对项目延期?" → 答:分析延期原因,调整任务优先级,申请资源支持,与团队沟通调整计划
  • "如何保证项目质量?" → 答:代码审查、单元测试、集成测试、代码规范、定期重构

6.4 技术选型与架构设计 ​

❓ 问题:前后端分离架构的优势? ​

标准回答:

架构优势:

  1. 技术栈解耦:前后端可以使用不同的技术栈
  2. 并行开发:前后端可以同时开发,提高开发效率
  3. 独立部署:前后端可以独立部署,互不影响
  4. 团队分工:前后端团队可以独立工作,职责清晰

技术选型:

  • 前端:Vue、React、Angular 等框架
  • 后端:Spring Boot、Node.js 等框架
  • 通信:RESTful API、GraphQL 等

实战经验: 在项目中,我们使用 Vue + Spring Boot 的前后端分离架构,前端独立部署,后端提供 RESTful API,开发效率高,维护方便。

深入追问可能问:

  • "前后端分离的挑战?" → 答:跨域问题、接口规范、联调测试、部署协调
  • "如何设计 RESTful API?" → 答:使用标准 HTTP 方法、合理的 URL 设计、统一的响应格式、版本控制

📝 面试准备 checklist ​

Java 基础 ​

  • [ ] Java 三大特性(封装、继承、多态)
  • [ ] HashMap vs Hashtable
  • [ ] synchronized vs volatile
  • [ ] ArrayList vs LinkedList
  • [ ] Java 内存模型(JMM)
  • [ ] 线程生命周期、sleep/wait/join 区别
  • [ ] synchronized vs ReentrantLock
  • [ ] 线程池参数与拒绝策略
  • [ ] 死锁排查与避免
  • [ ] JVM 内存结构(堆/栈/方法区/元空间)
  • [ ] 常见 OOM 与定位思路
  • [ ] GC 基本算法与调优思路(了解即可)

Spring Boot ​

  • [ ] Spring Boot 核心特性
  • [ ] Spring Boot 启动流程
  • [ ] 自动配置原理

MyBatis ​

  • [ ] MyBatis 执行流程
  • [ ] #{} 和 ${} 的区别
  • [ ] 一级缓存和二级缓存

MySQL ​

  • [ ] 索引数据结构(B+ 树)
  • [ ] 索引失效场景
  • [ ] 事务隔离级别、MVCC
  • [ ] 慢查询排查和优化

Redis ​

  • [ ] 数据类型和应用场景
  • [ ] 缓存穿透/击穿/雪崩(理解概念与方案即可)
  • [ ] 持久化机制(RDB vs AOF)
  • [ ] 分布式锁实现

前端 ​

  • [ ] 前后端分离架构
  • [ ] 跨域问题解决
  • [ ] Token 认证机制

部署运维 ​

  • [ ] Docker 和虚拟机的区别
  • [ ] Dockerfile 常用指令
  • [ ] Linux 常用命令
  • [ ] Docker 部署常见问题

项目问题 ​

  • [ ] 性能优化案例(数据库、接口、前端)
  • [ ] 线上问题排查方法
  • [ ] 物联网项目:整体架构、设备接入与鉴权、在线状态判断、数据表设计、告警规则
  • [ ] 远程工作协作经验
  • [ ] 项目管理经验
  • [ ] 技术选型理由

💡 使用建议:这份文档涵盖了专业技能中每个技术点可能被问到的问题和标准回答。建议:

  1. 先通读一遍,了解整体框架
  2. 针对你准备最充分的技术点,深入准备
  3. 准备具体案例,用 STAR 法则讲述
  4. 诚实回答,不会的就说"还在学习中"