java - 偶尔出现并发修改异常

标签 java multithreading

我有一个 JavaSE 库(以及对源代码的完全访问权限),在负载测试期间由无状态 EJB 执行时偶尔会抛出 ConcurrentModificationException。

应用程序服务器是 thorntail 2.3.0在 JDK 11.0.2 上运行

最初,异常是在编码(marshal)期间发生的(类似于 here ),但我添加了对以下方法的调用以查看异常也可能发生的位置:

    private static Object deepCopy(Object object) {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ObjectOutputStream outputStrm = new ObjectOutputStream(outputStream);
            outputStrm.writeObject(object); // line 164 in MyClass
            ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
            ObjectInputStream objInputStream = new ObjectInputStream(inputStream);
            return objInputStream.readObject();
        }
        catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

这是异常的堆栈跟踪示例:

java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList.writeObject(ArrayList.java:900)
    at java.base/jdk.internal.reflect.GeneratedMethodAccessor105.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at java.base/java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1130)
    at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1497)
    at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
    at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
    at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
    at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
    at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
    at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
    at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
    at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
    at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
    at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
    at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349)
    at com.my.java.se.library.MyClass.deepCopy(MyClass.java:164)
    at com.my.java.se.library.MyClass.someMethod(MyClass.java:132)
    at com.my.thorntail.app.SomeStatelessEJB.callToSeLib(SomeStatelessEJB.java:50)
   ....

我添加了对 deepCopy() 的调用在someMethod()的几个地方,并且异常有时会发生在 someMethod() 的最末尾。 ,有时在中间的某个地方。 我不知道我应该从中得出什么结论。是否有其他线程决定要修改该对象?

现在,整个JavaSE库不包含任何有关线程的语句,因此它是纯粹的单线程。据我了解,在单个线程中发生 ConcurrentModificationException 的唯一可能的选择是在迭代数组时修改数组。然而,事实并非如此。

此异常可能还有哪些其他原因? 我如何找出可能出现问题的地方?

编辑回应 Kayaman 的答案:

JavaSE 库实际上也只是我们开发的代码的一部分,因此调用代码和库代码同样可能是罪魁祸首。

我从 deepCopy() 的实验中得出结论方法,这会导致库内抛出 ConcurrentModificationExceptions,错误就在那里。但这可能是错误的。

这是调用代码的结构,在负载测试期间同时在许多线程中运行。在尝试将代码缩减为基本部分时,我意识到一个可能的问题:

@Stateless
public class SomeStatelessEJB {

   private MyClass myClass;

   public void callToSeLib() {
      myClass = getMyClassInstance(); // we are reusing the same MyClass object !!
      myClass.someMethod();
   }

}

我将调整代码以不再重用该对象,然后在这里报告。

编辑以报告进度

确实是MyClass对象的复用。

修复方法是仅重用 MyClass 对象的部分(创建成本高昂的部分),并为每个请求重新创建廉价且有问题的部分。

感谢您迫使我将代码压缩到最低限度,以便解释这里的情况。在此过程中我注意到了错误。

最佳答案

堆栈跟踪告诉您,在某个时刻您正在序列化一个 ArrayList,它同时被修改。 ArrayList中相关代码

    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }

因此,要么您的对象从不同的线程发生变异,要么您设法编写了导致列表在同一线程中通过不同方式发生变异的代码。在单线程情况下,如果元素有自己的 writeObject() 来修改列表,则可以得到这个(当然,这在代码中是一件可怕的事情)。 p>

你说这个库不使用线程,但你仍然根据你的环境使用线程,所以我们暂时不要放弃最简单的解释。

也许您应该展示如何调用 MyClass.someMethod(),因为那是您的代码,并且始终是第一个嫌疑人(在责怪自己之前不要责怪库)。

关于java - 偶尔出现并发修改异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58132750/

相关文章:

java - 在Java中将base25字符串转换为二进制字符串

java - 在冲突的情况下, spring.application.name 在 bootstrap.yml 中不被尊重,而是在 application.properties 中被尊重

c# - 阻塞的线程可以触发事件吗?

java - 在 Java 中对大文件执行 "File to byte[]"时出现 OutOfMemoryError 错误?

java - 写一个 public int compareTo() 方法 java

java - 将行插入数据库后 JTable 不更新

python - 执行线程 5 by 5

java - 优秀的多线程 Java 代码示例?

同步、 volatile 和(标记)锁的 Java 内存模型交互

java - ThreadPoolExecutor 在低负载时不收缩