Java子类访问父类缓存Process时显示进程已退出问题排查
问题描述
定义抽象类A,类中维护一个protected修饰的Hashtable类型成员变量processCache,用于存储类A内部启动的进程对应的Process实例。类B继承自类A,在类B的重写方法中取出processCache中的Process实例时,判断得到进程已退出;但如果在类A内部访问同一个Process实例,进程处于存活状态且可正常获取执行结果。
初始问题代码如下:
public abstract class A { protected final Map<String, Process> processCache = new Hashtable<>(); public void processResults(String processId) { getScanProcessResult(processId); // other logic } protected abstract String getScanProcessResult(String id); } public class B extends A { @Override protected String getScanProcessResult(String processId) { Process process = super.processCache.get(processId); if (process.isAlive) { // 实际判断为进程不存活 } } }
功能实现场景:
系统共配置2个批处理任务:
- 第一个任务启动若干进程,并将对应的
Process对象存入processCache - 第二个任务在首个任务启动1分钟后触发,从缓存的
Process对象中读取进程执行结果
如果将判断逻辑直接写在类A的processResults方法中,功能可正常运行,对应代码如下:
public void processResults(String processId) { Process process = processCache.get(processId); if (process.isAlive) { // 实际判断为进程存活 } // other logic }
问题根因
按出现概率从高到低排序,核心原因有以下几类:
成员变量遮蔽(Field Shadowing,最高概率)
实际业务代码中,类B自行定义了和父类A同名的processCache成员变量,和父类的processCache是完全独立的两个实例:- 父类A启动进程、存入缓存、以及在
processResults方法中访问的,都是父类A中定义的processCache,里面存的是刚启动的存活Process实例 - 你在类B的重写方法中,遗漏了
super.前缀,没有访问父类的缓存,而是直接访问了B类自己定义的processCache,这个缓存里存的是历史执行完成、已经退出的旧Process实例,因此判断为不存活。你给出的示例代码中写了super.processCache属于简化后的写法,和实际代码存在偏差。
- 父类A启动进程、存入缓存、以及在
多实例缓存隔离(次高概率)
你使用的批处理框架(Quartz、Spring Batch、XXL-JOB等)没有将类A/B配置为单例模式,每次触发任务时都会创建新的类实例:- 第一个任务启动时创建的实例,往自己持有的
processCache里存入了存活的Process实例 - 第二个任务触发时创建了全新的B实例,这个新实例的
processCache中存储的是历史已退出的旧进程实例,因此判断为不存活
你在A类的processResults方法中测试时,是在同一个实例的调用链内完成存、取、判断全流程,没有跨实例访问,因此可以正常拿到存活的进程实例。
- 第一个任务启动时创建的实例,往自己持有的
其他低概率原因
- 子类重写方法中,拿到
Process实例后提前执行了关闭流、调用waitFor()、触发进程销毁的逻辑,导致进程提前退出 - 集群部署批处理任务时未配置任务路由,两个任务被调度到不同集群节点执行,跨节点无法访问其他节点JVM内存中存储的
Process实例,拿到的是本节点存储的历史已退出进程 - 代码编写错误:将
Process.isAlive()方法调用误写为字段访问process.isAlive,没有真正执行进程存活判断逻辑,拿到的是错误的默认值。
- 子类重写方法中,拿到
内容的提问来源于stack exchange,提问作者aminbhst
相关产品推荐
相关产品推荐

