解析apache-commons-chains的Catalog XML时偶发EmptyStackException求助
解决Apache Commons Chains解析XML时的间歇性EmptyStackException问题
看起来你遇到的是Apache Commons Digester的线程安全问题——这也是为什么本地单线程/低并发调试复现不了,但在WebLogic这类多线程应用服务器上会间歇性触发EmptyStackException的核心原因。
问题根因分析
先看你的异常栈:异常出现在Digester.endElement()调用ArrayStack.pop()时栈为空。结合你的代码和场景,问题出在这几点:
Digester类本身不是线程安全的:它依赖内部栈跟踪XML解析的元素层级,多线程并发操作时,栈的push/pop会互相干扰。- 你的
getCatalog()懒加载逻辑存在并发风险:当多个线程同时进入方法(此时catalog还未初始化),会共享同一个parser实例调用parse(),导致Digester的内部栈被不同线程的操作打乱,最终某个线程执行pop()时栈已经被清空,抛出异常。
你的核心代码存在的并发隐患:
public Catalog getCatalog() throws Exception { if (catalog == null) { // 多个线程可能同时调用共享的parser实例的parse方法 parser.parse(IAFullEngineWorkFlow.class.getClassLoader().getResource("/resource URI/")); catalog = CatalogFactoryBase.getInstance().getCatalog(); } return catalog; }
可行解决方案
针对这个问题,你可以选择以下几种修复方式:
1. 每次解析创建独立的ConfigParser/Digester实例
不要共享parser实例,每次需要解析时创建新的实例,确保每个线程的解析操作完全隔离:
public Catalog getCatalog() throws Exception { if (catalog == null) { // 每次解析都新建ConfigParser,内部Digester也会是独立实例 ConfigParser parser = new ConfigParser(); parser.parse(IAFullEngineWorkFlow.class.getClassLoader().getResource("/resource URI/")); catalog = CatalogFactoryBase.getInstance().getCatalog(); } return catalog; }
2. 给懒加载逻辑加线程同步锁
如果不想重复创建实例,可以通过同步锁确保同一时间只有一个线程执行解析操作:
// 方法级同步,简单直接 public synchronized Catalog getCatalog() throws Exception { if (catalog == null) { parser.parse(IAFullEngineWorkFlow.class.getClassLoader().getResource("/resource URI/")); catalog = CatalogFactoryBase.getInstance().getCatalog(); } return catalog; }
或者用更高效的双重检查锁定(注意catalog要声明为volatile,避免指令重排问题):
private volatile Catalog catalog; public Catalog getCatalog() throws Exception { if (catalog == null) { synchronized (this) { // 二次检查,避免多线程重复初始化 if (catalog == null) { parser.parse(IAFullEngineWorkFlow.class.getClassLoader().getResource("/resource URI/")); catalog = CatalogFactoryBase.getInstance().getCatalog(); } } } return catalog; }
3. 提前初始化Catalog(推荐)
在应用启动阶段就完成Catalog的初始化,彻底避免懒加载带来的并发问题,比如:
// 静态代码块,应用启动时执行 private static Catalog catalog; static { try { ConfigParser parser = new ConfigParser(); parser.parse(IAFullEngineWorkFlow.class.getClassLoader().getResource("/resource URI/")); catalog = CatalogFactoryBase.getInstance().getCatalog(); } catch (Exception e) { throw new RuntimeException("Failed to initialize Catalog", e); } } public Catalog getCatalog() { // 直接返回已初始化的实例,无并发风险 return catalog; }
如果是Spring环境,也可以用@PostConstruct注解在Bean初始化时完成解析。
验证建议
修复后可以在WebLogic测试环境模拟高并发场景(比如用压测工具),确认EmptyStackException不再出现。
内容的提问来源于stack exchange,提问作者Sahil
相关产品推荐
相关产品推荐

