Java面试场景题案例

wen java案例 3

本文目录导读:

Java面试场景题案例

  1. 场景题一:设计一个短链接系统(系统设计 + 基础)
  2. 场景题二:OOM排查实战(JVM调优 + 性能排查)
  3. 场景题三:分布式锁的实现(并发 + 分布式)
  4. 场景题四:线程池拒绝策略导致任务丢失(并发 + 生产故障)
  5. 场景题五:数据库分库分表后跨库查询(架构设计)
  6. 附加建议

下面为你提供 5个高频Java面试场景题,涵盖设计、调优、并发、排查和架构方面,并给出解题思路和代码示例。


场景题一:设计一个短链接系统(系统设计 + 基础)

面试官问:如果让你设计一个类似 bit.ly 的短链接服务,你如何保证短链接不重复、高并发下不冲突,并且能快速跳转?

思路解析

  1. 生成短码:使用 Base62(数字+大小写字母)编码自增ID;或使用 MurmurHash 对原URL取哈希,再截断。
  2. 冲突处理:自增ID天然不冲突;哈希方案需要回查数据库或用布隆过滤器预判。
  3. 存储:MySQL建表 (short_code, original_url, created_at),主键用 short_code,并加缓存。
  4. 缓存:使用 Redis 做两级缓存(短码→原URL),用 Caffeine 做本地缓存,降低Redis压力。
  5. 跳转:302 重定向(便于统计点击量)。

代码示例(核心部分)

// Base62 编码
public class Base62Encoder {
    private static final String BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    public static String encode(long num) {
        StringBuilder sb = new StringBuilder();
        while (num > 0) {
            sb.append(BASE62.charAt((int) (num % 62)));
            num /= 62;
        }
        return sb.reverse().toString();
    }
    public static long decode(String str) {
        long num = 0;
        for (char c : str.toCharArray()) {
            num = num * 62 + BASE62.indexOf(c);
        }
        return num;
    }
}

场景题二:OOM排查实战(JVM调优 + 性能排查)

面试官问:线上应用突然发生 OutOfMemoryError: Java heap space,你会怎么排查?

回答要点(逐步排查)

  1. 保留现场:加 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof 参数,让JVM自动导出堆快照。
  2. 用MAT分析
    • 打开堆转储文件,看 Dominator Tree(支配树),找到占用最大内存的对象。
    • 检查是内存泄漏(Leak Suspects)还是内存溢出(对象确实都还活着,只是太多)。
  3. 区分场景
    • 泄漏:查看GC roots引用链,找到是谁强引用了这个对象(如静态集合未清理)。
    • 溢出:比如一次性加载大文件到内存,或分页查询时 offset 过大导致缓存穿透。
  4. 代码层面排查:检查是否有 ThreadLocal 未remove、是否有 InputStream 未关闭、是否有大集合未置空。

代码示例(一个典型泄漏案例)

public class MemoryLeakDemo {
    // 静态List持有对象,导致无法回收
    public static List<Object> leakList = new ArrayList<>();
    public void leak() {
        while (true) {
            Object obj = new Object();
            leakList.add(obj);  // 一直在增加,从不清理
        }
    }
}

场景题三:分布式锁的实现(并发 + 分布式)

面试官问:在分布式系统中,如何保证多个服务实例对同一个资源操作的互斥性?

回答要点

  1. 基于 Redis SETNX
    • 命令:SET lock_key unique_value NX PX 30000
    • 释放:用Lua脚本保证原子性(先判断value是否一致再DEL)。
    • 问题:主从切换时可能丢锁;需要续期(看门狗机制)。
  2. 基于 ZooKeeper 临时有序节点
    • 每个客户端创建临时有序节点,取序号最小的获得锁,后续节点监听前一个节点。
    • 优点:没有锁过期时间,不会死锁;缺点:性能比Redis略差。
  3. 最佳实践:Java中直接用 RedissonRLock,自带看门狗续期。

代码示例(Redisson 实现)

@Autowired
private RedissonClient redissonClient;
public void doWithLock(String key) {
    RLock lock = redissonClient.getLock(key);
    try {
        // 尝试加锁,最多等待5秒,锁有效期30秒(看门狗会自动续期)
        boolean isLocked = lock.tryLock(5, 30, TimeUnit.SECONDS);
        if (!isLocked) {
            throw new RuntimeException("获取锁失败");
        }
        // 业务逻辑...
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

场景题四:线程池拒绝策略导致任务丢失(并发 + 生产故障)

面试官问:某次线上高峰期,使用 Executors.newFixedThreadPool(10) 处理异步任务,发现大量任务被丢弃,你怎么处理?

问题分析

  1. 根本原因newFixedThreadPool 默认使用 LinkedBlockingQueue(无界队列),但队列满了后会用 AbortPolicy(抛异常)丢弃任务。
  2. 改进方案
    • 自定义线程池,使用 有界队列 + CallerRunsPolicy(让提交任务的线程自己执行),避免任务丢失与雪崩。
    • 做好 拒绝监控:在拒绝时打印日志或发送告警。

代码示例(自定义线程池)

public class ThreadPoolConfig {
    public static ExecutorService createPool() {
        int core = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            core,                    // 核心线程数
            core * 2,                // 最大线程数
            60L, TimeUnit.SECONDS,   // 空闲回收时间
            new ArrayBlockingQueue<>(1000),  // 有界队列
            new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build(),
            new CallerRunsPolicy()   // 拒绝策略:调用者执行
        );
        return executor;
    }
}

场景题五:数据库分库分表后跨库查询(架构设计)

面试官问:订单表数据量达到亿级,做分库分表后(按 user_id hash),如何做订单列表查询(跨分片)和订单详情查询?

回答要点

  1. 方案设计
    • 订单详情:订单ID应包含分片键信息(如 user_id + 序号),或使用 UID 生成器(如 snowflake),查详情时直接路由到对应分片。
    • 订单列表:如果按 user_id 分片,那么一个用户的订单都在同一分片,直接用 WHERE user_id = ? 查询即可。
    • 跨分片查询:如后台运营需要查全量订单,使用 中间层聚合(如 ShardingSphere 联邦查询),或同步到 Elasticsearch 做宽表查询。
  2. 注意事项
    • 避免跨分片 JOIN:尽量把关联表也按相同维度分片。
    • 使用 全局ID:雪花算法或号段模式,保证全局唯一。

代码示例(分片键路由思路)

public class OrderService {
    // 根据订单ID最前面几位截取分片号,如 user_id % 16
    public int getShardByUserId(Long userId) {
        return (int) (userId % 16);
    }
    public Order getOrderById(Long orderId, Long userId) {
        int shard = getShardByUserId(userId);
        // 直接路由到对应分片库的表
        return orderMapperShard[shard].selectByOrderId(orderId);
    }
}

附加建议

面试时,场景题不要直接说“我不会”,而是按以下步骤回答:

  1. 澄清需求:问清是功能问题、性能问题还是稳定性问题。
  2. 给出方案:说一两个可行的技术方案。
  3. 对比利弊:分析每个方案的优劣势与适用场景。
  4. 落地实施:给出关键代码或伪代码。
  5. 验证优化:如何测试、监控、回滚。

如果你有想针对某个方向(如秒杀、消息队列、微服务链路追踪)深入了解的,可以告诉我,我可以给你更细致的场景题!

抱歉,评论功能暂时关闭!