Java实习面试记录

JMM 中的三大特性

可见性

多个线程访问同一变量时,一个线程修改的值,其他线程能否及时看到

  • volatile 能保证可见性:

    volatile boolean flag = false;

    public void stopThread() {
    flag = true;
    }

    public void run() {
    while (!flag) {
    // do something
    }
    }

原子性

一个操作是否不可中断?

  • i++ 不是原子操作,需要使用 AtomicIntegersynchronized 保证原子性。

有序性

编译器/CPU 可能会指令重排volatile 禁止重排序,synchronized 同样具有顺序性保障。


ReentrantLock vs synchronized

对比项 synchronized ReentrantLock
可重入性
公平锁 ✅ 可选
可中断
读写锁支持 ✅ 可搭配 ReadWriteLock
性能 JDK1.6后优化较好 控制更灵活但稍复杂

场景:当需要响应中断超时控制公平锁机制 时,优先使用 ReentrantLock


高并发线程安全计数器设计

场景:库存扣减、秒杀扣量

  • 方案:AtomicIntegerLongAdder(高并发推荐)配合 Redis + Lua 保证分布式原子性。
  • 如果涉及分布式系统:
    • Redis 原子扣减 + 库存回滚
    • DB乐观锁(version字段)+ 唯一索引幂等性 + 异步 MQ 补偿机制

2. Spring框架与设计模式

三级缓存解决循环依赖

三级缓存结构:

复制代码
1. 一级缓存:singletonObjects (成品)
2. 二级缓存:earlySingletonObjects(提前暴露的半成品)
3. 三级缓存:singletonFactories(用于生成早期代理对象)

// 如果构造函数注入(如 @Autowired 构造器)发生循环依赖,无法提前暴露半成品对象,三级缓存失效。

解决方案:推荐使用字段注入或 @PostConstruct 避免构造器注入的循环依赖。


🔹 JDK动态代理 vs CGLIB

特性 JDK Proxy CGLIB
是否需要接口
代理机制 基于接口,反射生成代理类 继承目标类,字节码增强
性能 接口多时快 复杂类时略慢
Spring选择 有接口 → JDK,否则 → CGLIB

责任链模式优惠券参数校验(伪代码)

复制代码
public interface Handler {
    void setNext(Handler handler);
    void handle(Request request);
}

public class AmountCheckHandler implements Handler {
    public void handle(Request req) {
        if (req.amount < 0) throw new IllegalArgumentException();
        next.handle(req);
    }
}

优势:

  • 将每个校验逻辑解耦
  • 动态组合校验链(更灵活)
  • 新增规则无需修改原有代码,符合开放封闭原则

3. MySQL优化与高并发设计

SQL慢查优化流程

  1. 确认慢SQL
  2. EXPLAIN查看执行计划:
    • type:最佳是 const > ref > range > ALL
    • key:是否命中索引
    • rows:预估扫描行数
    • extra:是否出现 Using filesort, Using temporary
  3. 优化手段
    • 添加合适索引
    • 覆盖索引
    • 拆分大表
    • LIMIT 分页优化

分库分表后的跨分片查询

  • 手段:
    • 业务规避
    • 异步聚合(如日志、报表)
    • 中间件支持(如 ShardingSphere 分布式 SQL 解析 + 路由 + 聚合)

唯一索引 + 乐观锁幂等机制

插入前判断记录是否存在(幂等键),或插入失败即跳过。

复制代码
INSERT INTO coupon_log(user_id, coupon_id, request_id)
VALUES (...) ON DUPLICATE KEY UPDATE ...
  • 乐观锁:基于 version 字段,失败重试
  • 悲观锁:如 SELECT ... FOR UPDATE,加锁范围大,吞吐低
对比 乐观锁 悲观锁
并发性能
死锁风险 存在
应用场景 读多写少 严格强一致性

4. Redis与高可用架构

为什么用 Lua 实现原子性?

  1. 所有命令作为一个事务性脚本执行
  2. 单线程模型天然避免并发冲突
  3. MULTI/EXEC 不同,Lua 是"不可打断+打包执行"

Redis 脑裂问题与防范

脑裂后主写成功,主挂掉无法同步从节点 → 数据丢失。

防止写入孤立主:

复制代码
min-slaves-to-write: 2
min-slaves-max-lag: 10

表示只有在有 ≥2 个从节点延迟 <10s 时才允许主节点写入。


布隆过滤器误判率优化

误判 = 说有但其实没有

  • 调整参数:expectedInsertionsfalseProbability
  • 多哈希函数,合理内存分配
  • 将误判项记录到数据库(如灰度机制)
  • 定期重建布隆过滤器

5. 分布式系统与消息中间件

RocketMQ事务消息两阶段提交

  1. Half Message:消息先存储为"准备状态"
  2. 执行本地事务
  3. 事务回查
    • 成功 → Commit
    • 失败 → Rollback
    • 超时 → Broker 定期回查生产者状态

消费端幂等性方案

  • 通过唯一 msg_id/request_id 入库前判断(Redis Set / 去重表 + 唯一索引)
  • 如果高并发瓶颈:
    • Redis 先判断,再异步入库(最终一致)
    • 可设置 TTL 或滑动窗口做清理

MQ堆积延迟排查

  1. 查看消费速率和消费端堆积量
  2. 检查消费者是否:
    • 死循环/业务异常
    • 并发度设置过低
  3. 手段:
    • 扩容消费者
    • 提升消费能力
    • 消息分区优化(或分topic)

6. 短链接系统设计

短链生成算法对比

方法 优点 缺点
Hash(MD5, SHA) 简单、可分布式 存在哈希冲突,需重试
Snowflake 唯一性强、时间有序 不适合短码(太长)
数据库自增 简单 分布式不方便
base62编码 将ID压缩为短字符串 需考虑冲突与排序性

推荐组合:Snowflake生成ID + Base62编码


短链唯一性与哈希冲突处理

  1. 哈希后检查数据库是否存在
  2. 存在则重新哈希/加盐或加冲突计数
  3. 记录 原始URL+生成短码 的映射表

QPS 10万级别缓存策略

  1. Redis 做短链跳转缓存(Key: shortCode → URL)
  2. 热点数据加入本地 Caffeine 缓存
  3. 缓存穿透 → 布隆过滤器 + 缓存空值
  4. 异步写DB,日志落盘异步归档

7. 开放性问题

单元测试发现Bug案例

在 Mini-Spring 实现 AOP 时,某个切面注入出现空指针。测试时通过 mock bean 提前暴露出代理对象未生效的问题,进而修复为通过三级缓存暴露代理后的对象。


技术选型分歧如何协调

  1. 收集团队观点 + 论证维度(性能、生态、学习成本)
  2. 快速 POC 小范围验证
  3. 从场景适配角度做最终决策,而非"好恶"
  4. 若有博弈,保留少数意见作为 Plan B,确保应变能力

深入秒杀系统项目技术细节

以下针对"优惠券秒杀系统"的核心技术点进行深度解析与场景化问答:


1. 责任链模式在参数校验中的实现

Q:责任链模式如何保证优惠券创建流程的扩展性?如果新增一个"黑名单用户校验"节点,如何最小化代码改动?

  • 答案
    • 核心实现

      1. 定义统一的校验接口,每个校验器实现该接口并持有下一个节点的引用。

      2. 校验失败时抛出异常,成功则调用链的下一个节点。

        public interface CouponValidator {
        void validate(CouponRequest request) throws ValidationException;
        }
        public class ParamValidator implements CouponValidator {
        private CouponValidator next;
        public void validate(CouponRequest request) {
        if (request.getAmount() <= 0) throw new ValidationException("金额无效");
        if (next != null) next.validate(request);
        }
        // 设置下一个校验器
        public void setNext(CouponValidator next) { this.next = next; }
        }

    • 扩展性 :新增校验器只需实现CouponValidator接口,并通过setNext插入到责任链中。例如新增BlacklistValidator

      复制代码
      CouponValidator chain = new ParamValidator();
      chain.setNext(new InventoryValidator());
      chain.setNext(new BlacklistValidator()); // 新增节点
    • 优势:无需修改已有校验逻辑,符合开闭原则(OCP)。


2. 执行点记录与断点续发机制

Q:如何设计"执行点记录"机制来支持故障恢复?如果发放优惠券时系统宕机,重启后如何快速恢复?

  • 答案
    • 数据结构设计: 在数据库中记录发放批次的关键信息:

      复制代码
      CREATE TABLE coupon_batch (
          batch_id VARCHAR(64) PRIMARY KEY,
          total_count INT,
          sent_count INT,         -- 已发放数量
          last_user_id VARCHAR(64) -- 最后处理的用户ID
      );
    • 恢复流程

      1. 系统重启后,查询coupon_batch表获取未完成的批次。
      2. 根据last_user_id从断点处继续遍历用户列表,批量扣减库存并更新sent_count
    • 性能优化

      • 批量处理:每次发放100条优惠券,减少数据库写入压力。
      • 异步日志:通过RocketMQ异步记录发放进度,降低主流程延迟。

3. 幂等性保障方案

Q:如何通过"唯一索引+乐观自旋"实现RocketMQ消费端的幂等性?如果并发极高,自旋重试可能导致CPU飙升,如何优化?

  • 答案
    • 实现逻辑

      1. 消费消息前,尝试插入去重表(唯一索引为消息ID):

        INSERT INTO msg_dedup (msg_id) VALUES ('msg_001') ON DUPLICATE KEY UPDATE msg_id=msg_id;

      2. 若插入成功,继续处理;若触发唯一索引冲突,判定为重复消息,直接丢弃。

    • 自旋优化

      • 限制重试次数:例如最多重试3次,超过则记录日志并人工介入。
      • 退避策略:每次重试前增加等待时间(如指数退避:100ms → 200ms → 400ms)。

4. Redis ZSet在优惠券列表展示中的应用

Q:为什么选择ZSet存储用户已领取的优惠券?如果需支持按过期时间排序,如何设计Score?

  • 答案
    • ZSet优势
      • 天然支持按Score排序(如领取时间),查询时直接使用ZREVRANGE倒序获取。
      • 时间复杂度低:插入和范围查询均为O(log N)。
    • 过期时间排序
      • 将Score设置为优惠券过期时间戳(而非领取时间),查询时按Score范围过滤:

        复制代码
        ZADD user:coupons:1001 1672502400000 "coupon_001"  -- 2023-01-01过期
        ZRANGEBYSCORE user:coupons:1001 1672502400000 +inf  -- 查询未过期优惠券

5. Redis Lua脚本与原子性校验

Q:请写出资格校验的Lua脚本伪代码,并说明为何不直接用Redis事务(MULTI/EXEC)?

  • 答案
    • Lua脚本示例

      复制代码
      -- KEYS[1]: 库存键, KEYS[2]: 已领取用户集合, ARGV[1]: 用户ID
      local stock = tonumber(redis.call('GET', KEYS[1]))
      if stock <= 0 then
          return 0 -- 库存不足
      end
      if redis.call('SISMEMBER', KEYS[2], ARGV[1]) == 1 then
          return -1 -- 已领取
      end
      redis.call('DECR', KEYS[1])
      redis.call('SADD', KEYS[2], ARGV[1])
      return 1 -- 领取成功
    • Lua vs 事务

      • 原子性:Lua脚本执行期间不会被其他命令打断,事务中的命令可能被其他客户端插入执行。
      • 灵活性:Lua支持复杂逻辑(如条件判断、循环),事务仅支持简单命令队列。

6. 高并发下缓存穿透与击穿应对

Q:布隆过滤器如何解决缓存穿透?如果误判率较高,如何优化?

  • 答案
    • 布隆过滤器原理
      • 通过多个哈希函数将元素映射到位数组,查询时若任意一位为0,则元素一定不存在。
    • 误判率优化
      1. 增加哈希函数数量:降低冲突概率(但会增加计算开销)。
      2. 扩大位数组容量:减少哈希碰撞。
      3. 兜底策略 :缓存空值(key:null)并设置短TTL,拦截重复查询。

7. RocketMQ消息堆积处理

Q:如果消费端处理速度远低于生产端,导致消息大量堆积,如何快速解决?

  • 答案
    • 应急方案
      1. 扩容消费者:增加消费者实例或线程池大小。
      2. 批量消费 :调整consumeMessageBatchMaxSize参数,一次处理多条消息。
      3. 降级非核心逻辑:例如先缓存领取记录,异步持久化到数据库。
    • 根本解决
      • 优化消费逻辑:避免同步阻塞操作(如远程调用),改用异步或并行处理。
      • 限流保护:通过Sentinel对消费者限流,避免雪崩。

总结:面试回答技巧

  1. 细节具体化:结合代码、数据结构和流程图说明实现(如"三级缓存解决循环依赖")。
  2. 场景绑定:强调设计背后的业务需求(如"秒杀场景要求原子性,因此选择Lua脚本")。
  3. 问题反思:主动提及遇到的挑战(如"初期未考虑构造器注入循环依赖,通过单元测试发现并修复")。
  4. 性能数据:若有压测结果,可量化说明优化效果(如"引入Redis后,QPS从1k提升至10k")。
相关推荐
缺点内向12 小时前
Java:创建、读取或更新 Excel 文档
java·excel
带刺的坐椅13 小时前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看14 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程14 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t14 小时前
ZIP工具类
java·zip
lang2015092815 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan15 小时前
第10章 Maven
java·maven
百锦再16 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说16 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多16 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring