Java面试高频问题深度解析:JVM、锁机制、SQL优化与并发处理

71 阅读4分钟

问题列表

  1. Java中如何实现一个工作流引擎?
  2. Bean的作用域有哪些?
  3. JVM中的锁机制是如何工作的?
  4. 三个方法分别被 synchronized 锁住,方法 a 调用方法 bb 能获取到 a 的锁吗?会有什么问题?
  5. SQL优化时,EXPLAIN 中需要关注哪些关键点?
  6. 什么是覆盖索引?
  7. SELECT * 一定不会命中索引吗?
  8. SELECT * 和 SELECT 全字段 在性能上有区别吗?
  9. 什么是回表?它与索引有什么关系?
  10. 100万数据分给10个线程处理,如何实时获取每个线程的进度?

详细解答

1. Java中如何实现一个工作流引擎?

核心设计思想

  • 流程定义与执行分离:通过XML/JSON定义节点与流转规则,运行时解析生成实例。
  • 状态驱动模型:使用状态模式或有限状态机(FSM)管理流程状态。
  • 持久化与扩展:保存实例数据到数据库,支持插件化扩展(如自定义节点)。

实现步骤

  1. 定义流程模型

    public class WorkflowDefinition {
        private String id;
        private Map<String, Node> nodes; // 节点集合
    }
    
  2. 流程实例管理

    public class ProcessInstance {
        private String currentState;
        private Map<String, Object> variables; // 流程上下文
    }
    
  3. 任务调度与异步处理

    • 使用线程池处理耗时任务(如调用外部服务)。
    • 定时任务扫描待处理节点。

高级优化

  • 分布式协调:通过Redis实现分布式锁和事件总线。
  • 性能优化:批量处理实例状态变更,多级缓存(本地+Redis)。

2. Bean的作用域

Spring支持的Bean作用域及适用场景:

作用域生命周期典型场景
singleton容器级单例,全局共享无状态服务(如工具类)
prototype每次请求创建新实例有状态对象(如用户会话数据)
requestHTTP请求生命周期Web请求上下文(如表单数据)
session用户会话生命周期购物车、用户登录状态
applicationServletContext生命周期全局配置类
websocketWebSocket会话生命周期实时通信会话管理

3. JVM中的锁机制

锁升级过程

  1. 偏向锁:无竞争时记录线程ID,消除同步开销。
  2. 轻量级锁:CAS尝试获取锁,失败后自旋。
  3. 重量级锁:依赖操作系统互斥量(Mutex),线程阻塞。

锁优化技术

  • 锁消除:逃逸分析后移除不必要的锁。
  • 锁粗化:合并相邻同步块。
  • 适应性自旋:动态调整自旋次数。

4. 可重入锁与潜在问题

  • 答案b() 可以获取 a() 的锁(锁可重入)。

  • 问题

    • 锁粒度过粗:所有方法共享同一锁,降低并发性能。
    • 死锁风险:若方法递归调用且未合理退出,可能导致栈溢出。
  • 优化方案:使用细粒度锁或 ReentrantLock


5. SQL优化:EXPLAIN 关键点

核心字段

字段优化意义
type访问类型(避免 ALL 全表扫描)
key实际使用的索引(对比 possible_keys
rows预估扫描行数(越小越好)
Extra额外信息(警惕 Using filesort/temporary

优化案例

  • 全表扫描:为 WHERE 条件字段添加索引。
  • 文件排序:为 ORDER BY 字段添加索引。

6. 覆盖索引

定义与示例

  • 定义:索引包含查询所需的所有字段,无需回表。

  • 示例

    -- 创建覆盖索引
    CREATE INDEX idx_user_cover ON user(name, age, email);
    -- 查询命中覆盖索引
    SELECT name, age FROM user WHERE name = 'Alice';
    

7. SELECT * 是否命中索引?

  • 不一定:若使用覆盖索引且索引包含所有字段,SELECT * 可命中。

  • 示例

    -- 表 user 有主键 id 和索引 (id, name)
    SELECT * FROM user WHERE id = 1; -- 命中索引
    

8. SELECT * vs SELECT 全字段

  • 无覆盖索引:性能相同(均需回表)。
  • 有覆盖索引SELECT 全字段 性能更优(减少数据传输)。
  • 网络开销SELECT * 可能传输冗余字段。

9. 回表与索引

  • 回表:通过非聚集索引查询到主键后,再查聚集索引获取完整数据。

  • 索引结构

    • 聚集索引:叶子节点存储行数据(如主键索引)。
    • 非聚集索引:叶子节点存储主键值(需回表)。

10. 多线程进度监控

实现方案

  1. 任务拆分:按数据区间分配线程。

  2. 原子计数器

    public class ProgressTracker {
        private static AtomicLong processed = new AtomicLong(0);
        
        // 更新进度
        public static void update() { processed.incrementAndGet(); }
        
        // 获取进度百分比
        public static double getProgress() { return (double) processed.get() / TOTAL * 100; }
    }
    
  3. 扩展优化:分线程独立统计(ConcurrentHashMap<Thread, Long>)。


总结

本文涵盖了Java开发中高频的JVM、锁机制、SQL优化和并发处理问题,核心结论如下:

  • 锁机制:理解可重入性和锁升级过程,避免粗粒度锁。
  • SQL优化:通过 EXPLAIN 定位全表扫描和低效排序。
  • 索引设计:优先使用覆盖索引,减少回表开销。
  • 并发监控:原子类和分布式计数器是实时进度跟踪的关键。