【问题标题】:ConcurrentModificationException while calling size() on a sublist在子列表上调用 size() 时出现 ConcurrentModificationException
【发布时间】:2018-06-25 09:07:45
【问题描述】:

我正在尝试保存一堆 SQL 事务。我在 ESB 路由的上下文中,从 SQL 源传输到 SQL 目标,并且不能保证 SQL 事务的顺序,因此您可以在插入对象之前进行 SQL 更新。

由于架构的原因,我将这些 SQL 事务保存为 1000 x 1000(我正在使用消息队列)。所以其中一些可能会失败,我重新路由它们以重试或拒绝它们。为了提高效率,我愿意改进旧系统,如果1000失败,你一个一个保存,实现二分法(如果保存失败,你拆分列表重试) ,通过递归。我还在跟踪我的对象的一个​​属性,这要感谢另一个列表 (objectsNo) 的进一步操作。

但是,当我第一次递归调用 objectsList.size() 时,我得到了 ConcurrentModificationException。我怎样才能避免它?我也很开放,并且非常感谢任何能够提供除二分法之外的另一种方式来提高效率的解决方案(并且可以绕过我的问题)。

抑制:java.util.ConcurrentModificationException:null 在 java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231) 在 java.util.ArrayList$SubList.size(ArrayList.java:1040) 在 fr.company.project.esbname.mariadb.MariaDbDatabase.saveObjectWithDichotomie(MariaDbDatabase.java:398) 在 fr.company.project.esbname.mariadb.MariaDbDatabase.saveObjectWithDichotomie(MariaDbDatabase.java:404) 在 fr.company.project.esbname.mariadb.MariaDbDatabase.saveObject(MariaDbDatabase.java:350) 在 sun.reflect.GeneratedMethodAccessor324.invoke(未知来源) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.apache.camel.component.bean.MethodInfo.invoke(MethodInfo.java:472) 在 org.apache.camel.component.bean.MethodInfo$1.doProceed(MethodInfo.java:291) 在 org.apache.camel.component.bean.MethodInfo$1.proceed(MethodInfo.java:264) 在 org.apache.camel.component.bean.BeanProcessor.process(BeanProcessor.java:178) 在 org.apache.camel.management.InstrumentationProcessor.process(InstrumentationProcessor.java:77) 在 org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:541) ...省略了22个常用框架

我试着去理解,但应该不会有任何错误。即使我使用递归,它仍然是单线程的。我认为问题可能与休眠有关(来自保存失败的一些请求可能会保留在缓存中,并锁定修改),但问题在于大小,它位于原始列表的子列表中。

    private List<String> saveObjectWithDichotomie(List<Object> objects,
                                                            List<String> objectsNo,
                                                            Exchange exchange) throws JsonProcessingException {
    try {
        objectRepository.save(objects);

        return objectsNo;
    } catch (DataIntegrityViolationException e) {
        if (objects.size() == 1) {
            objectsNo.clear();
            errorProcessor.sendErrorToRejets(objects.get(0), exchange, e);

            return objectsNo;
        } else {
            List<Object> objectsFirstHalf = objects.subList(0, objects.size()/2);
            List<Object> objectsSecondHalf = objects.subList(objects.size()/2, objects.size());

            List<String> objectsNoFirstHalf = objectsNo.subList(0, objectsNo.size()/2);
            List<String> objectsNoSecondHalf = objectsNo.subList(objectsNo.size()/2, objectsNo.size());

            objectsNo.clear();

            objectsNo.addAll(
                    saveObjectWithDichotomie(objects, objectsNoFirstHalf, exchange)
            );
            objectsNo.addAll(
                    saveObjectWithDichotomie(objects, objectsNoSecondHalf, exchange)
            );

            return objectsNo;
        }
    }
}

【问题讨论】:

  • 您在删除或添加某些内容时正在更改您的列表,这就是您收到异常的原因
  • 您可能是对的,但我不明白为什么,我只是在列表上调用 size,然后对其进行子列表。我既不删除也不添加任何东西。我之前搜索过类似的问题,我看到了像你这样的答案,但我认为这与我的问题无关。
  • 这似乎与您使用 sublist 的事实有关,该列表记录了该列表的支持,因此其中一个的更改会反映到另一个中,反之亦然,我 认为你应该创建一个新的List,比如List&lt;Object&gt; objectsFirstHalf = new ArrayList&lt;&gt;(objects.subList(0, objects.size()/2));
  • 非常感谢您的回答!我不知道 sublist 是源列表中的参考。我会尝试你的解决方案。如果可行,请考虑回答问题,以便我接受您的回答。
  • 发布了答案...欢迎您

标签: java performance hibernate exception


【解决方案1】:

如果您阅读sublist 的文档,则清楚地说:

返回的列表是由这个列表支持的,所以返回列表中的非结构性变化会反映在这个列表中,反之亦然。

这就是您的异常的原因(不需要多个线程来实现)。因此,当您创建一个新列表时,请通过以下方式创建它:

List<Object> objectsFirstHalf = new ArrayList<>(objects.subList(0, objects.size()/2));

【讨论】:

    【解决方案2】:

    两件事:

    1. ConcurrentModificationException 并不意味着该列表已被另一个线程修改,而是某些东西试图以预期的状态访问该列表,但同时它已被更改。

    2. subList 不会创建实际的新列表,它会在原始列表上创建一个视图。这意味着您不能在不使检索到的子列表无效的情况下更改原始列表。

    所以,

    objectsNo.clear();
    

    是你的问题。

    查看此 MCVE:

    public class Sublist {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>(
                    IntStream.range(0, 100).mapToObj(Integer::toString).collect(Collectors.toList()));
    
            List<String> sublist = list.subList(10, 20);
    
            // outputs "15"
            System.out.println(sublist.get(5));
    
            list.clear();
    
            // throws ConcurrentModificationException
            System.out.println(sublist.get(5));
        }
    }
    

    【讨论】:

    • 谢谢大牛的回答,特别是你的例子,非常有帮助。但是我接受了 Eugene 的回答,因为他是第一个在 cmets 中回答我的人。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-10
    • 2023-03-10
    • 1970-01-01
    • 2021-12-15
    • 1970-01-01
    相关资源
    最近更新 更多