问题列表
- Java中如何实现一个工作流引擎?
- Bean的作用域有哪些?
- JVM中的锁机制是如何工作的?
- 三个方法分别被
synchronized锁住,方法a调用方法b,b能获取到a的锁吗?会有什么问题? - SQL优化时,
EXPLAIN中需要关注哪些关键点? - 什么是覆盖索引?
SELECT *一定不会命中索引吗?SELECT *和SELECT 全字段在性能上有区别吗?- 什么是回表?它与索引有什么关系?
- 100万数据分给10个线程处理,如何实时获取每个线程的进度?
详细解答
1. Java中如何实现一个工作流引擎?
核心设计思想
- 流程定义与执行分离:通过XML/JSON定义节点与流转规则,运行时解析生成实例。
- 状态驱动模型:使用状态模式或有限状态机(FSM)管理流程状态。
- 持久化与扩展:保存实例数据到数据库,支持插件化扩展(如自定义节点)。
实现步骤
-
定义流程模型:
public class WorkflowDefinition { private String id; private Map<String, Node> nodes; // 节点集合 } -
流程实例管理:
public class ProcessInstance { private String currentState; private Map<String, Object> variables; // 流程上下文 } -
任务调度与异步处理:
- 使用线程池处理耗时任务(如调用外部服务)。
- 定时任务扫描待处理节点。
高级优化
- 分布式协调:通过Redis实现分布式锁和事件总线。
- 性能优化:批量处理实例状态变更,多级缓存(本地+Redis)。
2. Bean的作用域
Spring支持的Bean作用域及适用场景:
| 作用域 | 生命周期 | 典型场景 |
|---|---|---|
singleton | 容器级单例,全局共享 | 无状态服务(如工具类) |
prototype | 每次请求创建新实例 | 有状态对象(如用户会话数据) |
request | HTTP请求生命周期 | Web请求上下文(如表单数据) |
session | 用户会话生命周期 | 购物车、用户登录状态 |
application | ServletContext生命周期 | 全局配置类 |
websocket | WebSocket会话生命周期 | 实时通信会话管理 |
3. JVM中的锁机制
锁升级过程
- 偏向锁:无竞争时记录线程ID,消除同步开销。
- 轻量级锁:CAS尝试获取锁,失败后自旋。
- 重量级锁:依赖操作系统互斥量(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. 多线程进度监控
实现方案
-
任务拆分:按数据区间分配线程。
-
原子计数器:
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; } } -
扩展优化:分线程独立统计(
ConcurrentHashMap<Thread, Long>)。
总结
本文涵盖了Java开发中高频的JVM、锁机制、SQL优化和并发处理问题,核心结论如下:
- 锁机制:理解可重入性和锁升级过程,避免粗粒度锁。
- SQL优化:通过
EXPLAIN定位全表扫描和低效排序。 - 索引设计:优先使用覆盖索引,减少回表开销。
- 并发监控:原子类和分布式计数器是实时进度跟踪的关键。