Drools规则并发执行时全局变量异常问题咨询及排查方案求助
问题分析
你的问题核心是全局集合的线程安全性问题,以及并发场景下对myList的共享访问导致的竞态条件:
- KieSession隔离性没问题,但全局变量共享了非线程安全集合
你每次执行规则都创建新的KieSession,这部分是正确的——KieSession本身线程不安全,单请求单实例是标准做法。但问题出在传入的myList:如果多个并发请求共享同一个非线程安全集合(比如普通ArrayList),当多个线程同时调用add()时,会出现数据丢失、元素覆盖或集合结构损坏的情况。 - 规则执行的原子性未被保证
即使每个请求用独立KieSession,但如果myList是跨请求共享的全局对象(比如Spring单例Bean中的集合),多线程对它的修改没有同步保护,add()操作非原子,并发下必然出现预期外结果。
解决建议
根据业务场景,可选择以下修复方案:
1. 每个请求使用独立集合实例(推荐)
如果myList不需要跨请求共享,每次调用executeRules时创建新集合实例,从根源避免共享问题:
// 调用方代码示例 Map<String, Object> globals = new HashMap<>(); globals.put("myList", new ArrayList<>()); // 每个请求创建独立集合 ruleExecutor.executeRules(rulesProvider, applicant, globals);
2. 替换为线程安全集合
如果必须共享myList(比如收集所有请求的规则结果),将集合替换为线程安全实现:
Collections.synchronizedList(new ArrayList<>()):基础同步包装列表CopyOnWriteArrayList:适合读多写少场景ConcurrentLinkedQueue:队列结构需求时使用
规则中可明确全局变量类型(可选):
global java.util.concurrent.CopyOnWriteArrayList myList
传入全局变量时使用线程安全实例:
globals.put("myList", new CopyOnWriteArrayList<>());
3. 对集合操作加同步锁
若不想替换集合实现,可在规则的then块或Java代码中对集合修改加锁:
rule "myRule3" when Applicant(experienceInYears == 5) then synchronized(myList) { myList.add("level3"); } end
注意:该方式会降低并发性能,需权衡业务需求与性能损耗。
4. 改用Fact传递结果(符合Drools设计理念)
Drools推荐用Fact传递数据而非全局变量,定义结果类作为Fact插入会话,规则修改该对象的集合属性:
// 结果类定义 public class RuleResult { private List<String> levels = new ArrayList<>(); public void addLevel(String level) { levels.add(level); } public List<String> getLevels() { return levels; } }
规则中使用Fact代替全局变量:
rule "myRule3" when Applicant(experienceInYears == 5) $result: RuleResult() then $result.addLevel("level3"); end
Java代码中插入结果Fact并取出:
private void setupFactAndFireAllRules(Object fact, KieSession kieSession) { RuleResult result = new RuleResult(); kieSession.insert(result); kieSession.insert(fact); kieSession.fireAllRules(); List<String> levels = result.getLevels(); // 获取规则执行结果 }
并发测试定位根因
可通过以下方法模拟并发场景,验证问题并确认修复效果:
1. JUnit + ExecutorService模拟并发请求
@Test void testConcurrentRuleExecution() throws InterruptedException { int threadCount = 100; ExecutorService executor = Executors.newFixedThreadPool(threadCount); CountDownLatch latch = new CountDownLatch(threadCount); // 测试用:非线程安全集合会出现数据丢失;修复后替换为CopyOnWriteArrayList List<String> sharedList = new ArrayList<>(); for (int i = 0; i < threadCount; i++) { final int experience = (i % 3) == 0 ? 5 : (i % 3 == 1 ? 10 : 15); executor.submit(() -> { try { Applicant applicant = new Applicant(); applicant.setExperienceInYears(experience); Map<String, Object> globals = new HashMap<>(); globals.put("myList", sharedList); ruleExecutor.executeRules(rulesProvider, applicant, globals); } finally { latch.countDown(); } }); } latch.await(); executor.shutdown(); // 预期100个元素,非线程安全集合结果会少于100 System.out.println("Total elements: " + sharedList.size()); }
2. JMeter压力测试
- 创建线程组设置并发数(如100线程)
- 配置HTTP请求调用规则执行接口
- 检查响应中
myList的元素数量或内容是否符合预期
3. 启用Drools调试日志
添加日志配置查看规则执行顺序与集合修改细节:
logging.level.org.drools=DEBUG
通过日志可确认哪些规则被触发,以及myList的修改是否被正确执行,帮助定位竞态条件导致的数据丢失问题。
内容的提问来源于stack exchange,提问作者msdev
相关产品推荐
相关产品推荐

