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")。
相关推荐
杨DaB2 小时前
【SpringMVC】拦截器,实现小型登录验证
java·开发语言·后端·servlet·mvc
自由鬼3 小时前
如何处理Y2K38问题
java·运维·服务器·程序人生·安全·操作系统
_oP_i6 小时前
RabbitMQ 队列配置设置 RabbitMQ 消息监听器的并发消费者数量java
java·rabbitmq·java-rabbitmq
Monkey-旭6 小时前
Android Bitmap 完全指南:从基础到高级优化
android·java·人工智能·计算机视觉·kotlin·位图·bitmap
我爱996!6 小时前
SpringMVC——响应
java·服务器·前端
小宋10217 小时前
多线程向设备发送数据
java·spring·多线程
大佐不会说日语~8 小时前
Redis高频问题全解析
java·数据库·redis
寒水馨8 小时前
Java 17 新特性解析与代码示例
java·开发语言·jdk17·新特性·java17
启山智软8 小时前
选用Java开发商城的优势
java·开发语言
鹦鹉0078 小时前
SpringMVC的基本使用
java·spring·html·jsp