ConcurrentModificationException для ArrayList - PullRequest
0 голосов
/ 04 декабря 2018

Я получаю java.util.ConcurrentModificationException.

Связанный код:

for (Iterator<Audit> it = this.pendingAudits.iterator(); it.hasNext();) {
    // Do something
    it.remove();
}

Когда вызывается it.remove(), это исключает меня.

Этот код находится внутри аннотированного класса @Service:

@Service
public class AuditService {
    public AuditService(
        this.pendingAudits = new ArrayList<Audit>();
    }

    public void flush(Audit... audits) {
        this.pendingAudits.addAll(Arrays.asList(audits));

        try {
            for (Iterator<Audit> it = this.pendingAudits.iterator(); it.hasNext();) {
                // Do something
                it.remove();
            }
        }
    }
}

Проблема возникает, когда два запроса достигают кода.

Как можно избежать этого исключения одновременного доступа?

Ответы [ 3 ]

0 голосов
/ 04 декабря 2018

Разве это не очевидно?Вы делитесь данными без надлежащей синхронизации.

@ Сервисный аннотированный класс обычно является одноэлементным классом области действия и, следовательно, один и тот же экземпляр будет совместно использоваться всеми вызывающими потоками.

В результате все потоки получают доступ к методу flush в одном и том же экземпляре.

И угадайте, что?

Ваш метод flush пытается изменить ArrayList, который является переменной-членом.Это делает его небезопасным в многопоточном сценарии.

Настало время вернуться к документации ArrayList, в которой гораздо больше говорится о его итераторе.

Обратите внимание, что эта реализация несинхронизированы.Если несколько потоков обращаются к экземпляру ArrayList одновременно, и хотя бы один из потоков структурно изменяет список, он должен быть синхронизирован извне.(Структурная модификация - это любая операция, которая добавляет или удаляет один или несколько элементов или явно изменяет размер базового массива; просто установка значения элемента не является структурной модификацией.) Обычно это выполняется путем синхронизации с некоторым объектом, который естественным образом инкапсулируетсписок.Если такого объекта не существует, список следует «обернуть» с помощью метода Collections.synchronizedList.Это лучше всего делать во время создания, чтобы предотвратить случайный несинхронизированный доступ к списку: List list = Collections.synchronizedList (new ArrayList (...));Итераторы, возвращаемые методами итератора этого класса и метода listIterator, работают без сбоев: если список структурно изменяется в любое время после создания итератора, любым способом, кроме использования собственных методов удаления или добавления итератора, итератор создает исключение ConcurrentModificationException.Таким образом, перед одновременной модификацией итератор быстро и чисто дает сбой, вместо того, чтобы рисковать произвольным недетерминированным поведением в неопределенное время в будущем.Обратите внимание, что отказоустойчивое поведение итератора не может быть гарантировано, так как, вообще говоря, невозможно сделать какие-либо жесткие гарантии при наличии несинхронизированной параллельной модификации.Отказоустойчивые итераторы создают исключительную ситуацию ConcurrentModificationException.Следовательно, было бы неправильно писать программу, которая зависела от этого исключения в отношении его корректности: поведение итераторов, обеспечивающее отказоустойчивость, следует использовать только для обнаружения ошибок.

0 голосов
/ 05 декабря 2018

Я думаю, что вы, вероятно, поместили Audit в список pendingAudits где-нибудь, и вы хотите сбросить их все, когда вызываете метод flush (Audit ...), вы можете использовать ConcurrentLinkedQueue вместо ArrayList.

public AuditService(
    this.pendingAudits = new ConcurrentLinkedQueue<Audit>();
}

public void flush(Audit... audits) {
    this.pendingAudits.addAll(Arrays.asList(audits));

    try {
        Audit audit;
        while ((audit = this.pendingAudits.poll) != null) {
            // Do something
        }
    }
}
0 голосов
/ 04 декабря 2018

Перво-наперво, это не проблема Spring.Это просто проблема с одновременной модификацией одного, не столь параллельного, класса ArrayList.

Самым простым решением было бы синхронизировать доступ к методу, который его модифицирует.

public synchronized void flush(Audit... audits) { }

Имейте в виду, что он обеспечит последовательное выполнение метода flush, что влечет за собой огромные потери производительности.


Sidenote, недостаточно синхронизировать саму коллекцию с помощью Collections.synchronizedList - экземпляры итератора, возвращаемые синхронизированными оболочками, требуют ручной синхронизации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...