Spring 批处理管理在每次执行后抛出 ConcurrentModificationException

Spring Batch Admin throws ConcurrentModificationException after every execution

随着我对它的深入了解,我发现我喜欢 spring 批处理管理应用程序。

但是,在每次作业执行后,我都会看到抛出此异常。

环境

OS: Windows 7
Java: jdk 1.8.0_25
Spring Batch Admin Sample version: 1.3.1
Spring version: 3.2.13   * stock 3.2.9 has a bug that causes other symptoms 
Spring-batch version: 3.0.2
Pivotal tc version:  3.0 Developer Edition
IDE: STS 3.6.3

日志片段:

14:33:36.246 [pool-1-thread-1] ERROR o.s.s.s.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task.
java.util.ConcurrentModificationException: null
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) ~[na:1.8.0_25]
    at java.util.ArrayList$Itr.remove(ArrayList.java:865) ~[na:1.8.0_25]
    at org.springframework.batch.admin.service.SimpleJobService.removeInactiveExecutions(SimpleJobService.java:498) ~[spring-batch-admin-manager-1.3.1.MAXIS-MOD.jar:na]
    at sun.reflect.GeneratedMethodAccessor196.invoke(Unknown Source) ~[na:na]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_25]
    at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_25]
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:64) ~[spring-context-3.2.13.RELEASE.jar:3.2.13.RELEASE]
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:53) ~[spring-context-3.2.13.RELEASE.jar:3.2.13.RELEASE]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_25]
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [na:1.8.0_25]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access1(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_25]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_25]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_25]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_25]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_25]

我的代码没有触及spring批管理的核心,所以我有点困惑。

这还不是一个亮点,所以我愿意接受建议(包括最终贡献一个补丁)。

异常的原因是 org.springframework.batch.admin.service.SimpleJobService 的错误实现,至少在 1.3 中是这样。1.RELEASE:

private Collection<JobExecution> activeExecutions = Collections.synchronizedList(new ArrayList<JobExecution>());


public void removeInactiveExecutions() {
            for (Iterator<JobExecution> iterator = activeExecutions.iterator(); iterator.hasNext();) {
                JobExecution jobExecution = iterator.next();
    ...
                if (!jobExecution.isRunning()) {
                    iterator.remove();
                }
            }

java.util.ArrayList 上调用 iterator.remove(); 之后调用 iterator.getNext(); - 即使同步 - 也不是一个好主意...

我目前正在覆盖 Spring 批处理管理配置,使用我自己的 JobService 实现用于 id="jobService":

的 bean
META-INF\spring\batch\override\execution-context.xml

...
<bean id="jobService" class="com.foo.springbatch.MyPatchedSimpleJobServiceFactoryBean">
    <property name="jobRepository" ref="jobRepository" />
    <property name="jobLauncher" ref="jobLauncher" />
    <property name="jobLocator" ref="jobRegistry" />
    <property name="dataSource" ref="dataSource" />
</bean>
...

不确定为什么答案被接受。这是不正确的。 next() 的调用当然是在 hasNext() 之后。它是正确的。缺少同步以防止此方法的两个或多个计划调用可能发生冲突,以及 class 中其他位置的集合的其他修改。 Collections.synchronizedList(new ArrayList .. 他们拥有的是不够的。

刚遇到这个问题,一个简单的解决方法是将 org.springframework.batch.admin.service.SimpleJobService 中的代码更改为:

public void removeInactiveExecutions() {
        **synchronized (activeExecutions) {**
            for (Iterator<JobExecution> iterator = activeExecutions.iterator(); iterator.hasNext();) {
                JobExecution jobExecution = iterator.next();
                try {
                    jobExecution = getJobExecution(jobExecution.getId());
                }
                catch (NoSuchJobExecutionException e) {
                    logger.error("Unexpected exception loading JobExecution", e);
                }
                if (!jobExecution.isRunning()) {
                    iterator.remove();
                }
            }
        **}**
    }

上面的注释 ** 表示更改..