当在子列表上调用size()方法时出现ConcurrentModificationException异常

4
我正在尝试保存一些SQL事务。我在ESB路由的上下文中,从SQL源传输到SQL目标,SQL事务的顺序不能保证,因此您可以在对象插入之前进行SQL更新。
由于架构原因,我将这些SQL事务1000个1000个地保存(我正在使用消息队列)。因此,其中一些可能会失败,并且我会重新路由它们以重试或拒绝它们。为了提高效率,我愿意改进旧系统,在该系统中,如果1000个失败,则逐个保存1个,通过递归实现二分法(如果保存失败,则拆分列表并重试),我还通过另一个列表(objectsNo)跟踪我的对象的属性,以进行进一步操作。
但是,在第一次递归中调用objectsList.size()时,我遇到了ConcurrentModificationException。如何避免它?我也很开放,并且非常感谢任何解决方案,这些解决方案提供了除了二分法以外的提高效率的方法(并且将绕过我的问题)。
我尝试理解,但不应该有任何错误。即使我使用递归,它仍然是单线程的。我认为问题可能出在Hibernate上(一些来自失败的保存的请求可能会留在缓存中,并锁定修改),但问题在于size,它在原始列表的子列表上。
    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;
        }
    }
}

3
您在移除或添加列表内容时修改了列表本身,这就是导致异常的原因。 - Eugene
你可能是对的,但我不明白为什么。我只是在一个列表上调用size函数,然后创建了它的子列表。我既没有删除也没有添加任何东西。我之前搜索过类似的问题,看到了像你的答案一样的回复,但我认为那并不适用于我的问题。 - Borneheld
看起来这与您使用的 sublist 有关,它被记录为由此列表支持,因此一个列表中的更改会反映到另一个列表中,反之亦然。我认为您应该创建一个新的 List,例如 List<Object> objectsFirstHalf = new ArrayList<>(objects.subList(0, objects.size()/2)); - Eugene
非常感谢您的回答!我不知道子列表是源列表上的一个引用。我会尝试您的解决方案。如果它有效,请考虑回答问题,这样我就可以接受您的答案。 - Borneheld
已发布答案...不客气。 - Eugene
2个回答

5
如果您阅读sublist的文档,它明确表示:
返回的列表由此列表支持,因此在返回的列表中进行非结构性更改会反映在此列表中,反之亦然。
这就是您的异常原因(不需要多个线程才能发生)。因此,当您创建一个新列表时,请通过以下方式创建:
List<Object> objectsFirstHalf = new ArrayList<>(objects.subList(0, objects.size()/2));

2

有两件事情:

  1. ConcurrentModificationException并不意味着列表被另一个线程修改,而是某个正在尝试访问该列表的代码在期望它处于某种状态时发现它已经被修改了。

  2. subList并不会创建一个全新的列表,而是会在原始列表上创建一个视图。这意味着您不能更改原始列表而不使检索到的子列表无效。

因此,

objectsNo.clear();

你的问题是什么。
看看这个最小完整可复现示例:
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));
    }
}

1
谢谢您的回答,Daniu。特别是您的例子非常有帮助。但是我接受了Eugene的答案,因为他第一个在评论中回答了我。 - Borneheld

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接