ConcurrentModificationException при вызове size () в подсписке

Я пытаюсь сохранить кучу транзакций SQL. Я нахожусь в контексте маршрутов ESB при передаче из источника SQL в цель SQL, и порядок транзакций SQL не гарантируется, поэтому вы можете выполнить обновление SQL до того, как объект был вставлен.

Благодаря архитектуре я сохраняю эти SQL-транзакции 1000 на 1000 (я использую messageQueue). Так что некоторые из них могут выйти из строя, и я перенаправляю их, чтобы повторить попытку или отклонить их. Чтобы повысить эффективность, я готов улучшить старую систему, где в случае сбоя 1000 вы сохраняете 1 к 1, чтобы реализовать дихотомия (если сохранение не удалось, вы разделите список и повторите попытку) через рекурсивность. Я также отслеживаю атрибут своих объектов благодаря другому списку (objectsNo) для дальнейших операций.

Однако я получаю ConcurrentModificationException при первой рекурсивности при вызове objectsList.size (). Как мне этого избежать? Я также открыт и был бы очень благодарен любым решениям, которые предоставили бы другой способ, кроме дихотомии, улучшить эффективность (и таким образом обошли бы мою проблему).

Suppressed: java.util.ConcurrentModificationException: null at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231) at java.util.ArrayList$SubList.size(ArrayList.java:1040) at fr.company.project.esbname.mariadb.MariaDbDatabase.saveObjectWithDichotomie(MariaDbDatabase.java:398) at fr.company.project.esbname.mariadb.MariaDbDatabase.saveObjectWithDichotomie(MariaDbDatabase.java:404) at fr.company.project.esbname.mariadb.MariaDbDatabase.saveObject(MariaDbDatabase.java:350) at sun.reflect.GeneratedMethodAccessor324.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.camel.component.bean.MethodInfo.invoke(MethodInfo.java:472) at org.apache.camel.component.bean.MethodInfo$1.doProceed(MethodInfo.java:291) at org.apache.camel.component.bean.MethodInfo$1.proceed(MethodInfo.java:264) at org.apache.camel.component.bean.BeanProcessor.process(BeanProcessor.java:178) at org.apache.camel.management.InstrumentationProcessor.process(InstrumentationProcessor.java:77) at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:541) ... 22 common frames omitted

Я пытался понять, но ошибки быть не должно. Даже если бы я использовал рекурсивность, он остался однопоточным. Я считал, что проблема может быть в спящем режиме (некоторые запросы из сбойного сохранения могут остаться в кеше и заблокировать модификацию), но проблема связана с размером, который находится в подсписке исходного списка.

    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;
        }
    }
}

вы изменяете свой список при удалении или добавлении чего-либо в него, поэтому вы получаете исключение

Eugene 25.06.2018 11:11

Возможно, вы правы, но я не понимаю почему, я просто называю размер в списке, а затем делаю из него подсписок. Я ничего не удаляю и не добавляю. Я искал подобные проблемы раньше и видел ответы, подобные вашему, но я не думаю, что это имеет отношение к моей проблеме.

Borneheld 25.06.2018 11:14

похоже, что это связано с тем фактом, что вы используете sublist, который документально подтвержден этим списком, поэтому изменения в одном отражаются в другом и наоборот, я считать вам следует создать новый List, например List<Object> objectsFirstHalf = new ArrayList<>(objects.subList(0, objects.size()/2));

Eugene 25.06.2018 11:14

Большое спасибо за ответ ! Я не знал, что этот подсписок был ссылкой на исходный список. Я попробую ваше решение. Если это сработает, подумайте об ответе на вопрос, чтобы я мог принять ваш ответ.

Borneheld 25.06.2018 11:18

отправил ответ ... и милости просим

Eugene 25.06.2018 11:34
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
5
470
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));
    }
}

Спасибо daniu за ваш ответ, особенно за очень полезный пример. Однако я принял ответ Евгения, потому что он первым ответил мне в комментариях.

Borneheld 25.06.2018 11:39
Ответ принят как подходящий

Если вы прочитали документацию sublist, ясно сказано:

The returned list is backed by this list, so non-structural changes in the returned list are reflected in this list, and vice-versa.

Это является причиной вашего исключения (для этого нет необходимости в нескольких потоках). Таким образом, когда вы создаете новый список, создайте его с помощью:

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

Другие вопросы по теме