Ответ 1
Iterator
для класса HashSet
является отказоустойчивым итератором. Из документации HashSet
класс:
Итераторы, возвращаемые этим методом итератора класса, не работают быстро: если набор изменяется в любое время после создания итератора, в любым способом, кроме как через собственный метод удаления итератора, Итератор выдает исключение ConcurrentModificationException. Таким образом, перед лицом одновременная модификация, итератор терпит неудачу быстро и чисто, а не рисковать произвольным, недетерминированным поведением на неопределенное время в будущем.
Обратите внимание, что небезопасное поведение итератора не может быть гарантировано поскольку, вообще говоря, невозможно сделать какие-либо твердые гарантии в присутствии несинхронизированной параллельной модификации. Нормально-быстро итераторы бросают ConcurrentModificationException на лучшее усилие основа. Поэтому было бы неправильно писать программу, зависящую от по этому исключению для его правильности: отказоустойчивое поведение итераторы должны использоваться только для обнаружения ошибок.
Обратите внимание на последнее предложение - тот факт, что вы ловите ConcurrentModificationException
, подразумевает, что другой поток изменяет коллекцию. На той же странице API Javadoc также указано:
Если несколько потоков обращаются к хеш-набору одновременно и по меньшей мере один нитей изменяет набор, он должен быть синхронизирован извне. Обычно это выполняется путем синхронизации на некотором объекте, который естественно инкапсулирует набор. Если такой объект не существует, набор должен быть "завернут" с помощью метода Collections.synchronizedSet. Эта лучше всего делать во время создания, чтобы предотвратить случайную несинхронизированную доступ к набору:
Set s = Collections.synchronizedSet(new HashSet(...));
Я считаю, что ссылки на Джавадок объясняют себя тем, что должно быть сделано дальше.
Кроме того, в вашем случае я не понимаю, почему вы не используете ImmutableSet
вместо того, чтобы создавать HashSet на terms
объект (который может быть изменен промежутком времени, я не вижу реализации метода getTerms
, но у меня есть догадка, что базовый набор ключей изменяется). Создание неизменяемого набора позволит текущему потоку иметь собственную защитную копию исходного набора ключей.
Обратите внимание, что хотя a ConcurrentModificationException
можно предотвратить с помощью синхронизированного набора (как указано в документации по API Java), это является предварительным условием, что все потоки напрямую обращаются к синхронизированной коллекции, а не к базе данных резервного копирования (которая может быть неверно в вашем случае, поскольку HashSet
, вероятно, создается в одном потоке, тогда как базовая коллекция для MultiMap
изменяется другими потоками). Синхронизированные классы коллекций фактически поддерживают внутренний мьютекс для потоков, чтобы получить доступ; так как вы не можете получить доступ к мьютексу непосредственно из других потоков (и было бы довольно смешно делать это здесь), вы должны посмотреть на использование защитной копии либо набора ключей, либо самого MultiMap с помощью метода unmodifiableMultimap
класса MultiMaps
(вам нужно будет вернуть немодифицируемую MultiMap из метода getTerms). Вы также можете исследовать необходимость возврата synchronized MultiMap, но опять же, вам нужно убедиться, что мьютекс должен быть приобретен любым чтобы защитить базовую коллекцию от одновременных изменений.
Заметьте, я сознательно не упомянул об использовании потокобезопасного HashSet
по той только причине, что я не уверен, что одновременный доступ к фактическому сбор будет обеспечен; это, скорее всего, не будет.
Изменить: ConcurrentModificationException
брошен на Iterator.next
в однопоточном сценарии
Это относится к утверждению: if(c.isSomething()) C.remove(c);
, которое было введено в отредактированном вопросе.
Вызов Collection.remove
изменяет характер вопроса, так как теперь становится возможным вызывать ConcurrentModificationException
даже в однопоточном сценарии.
Возможность возникает из-за использования самого метода в сочетании с использованием итератора Collection
, в этом случае переменная it
, которая была инициализирована с помощью инструкции: Iterator<BooleanClause> it = C.iterator();
.
Iterator
it
, который выполняет итерацию по Collection
C
, сохраняет состояние, относящееся к текущему состоянию Collection
. В этом конкретном случае (при условии, что JRE Sun/Oracle) для Collection
используется KeyIterator
(внутренний внутренний класс класса HashMap
, который используется HashSet
). Особенностью этого Iterator
является то, что он отслеживает количество структурных изменений, выполненных в Collection
(HashMap
в этом случае) с помощью метода Iterator.remove
.
Когда вы вызываете remove
непосредственно на Collection
, а затем следуете за ним вызовом Iterator.next
, итератор выдает a ConcurrentModificationException
, так как Iterator.next
проверяет, не были ли какие-либо структурные модификации Collection
произошло, что Iterator
не знает. В этом случае Collection.remove
вызывает структурную модификацию, которая отслеживается Collection
, но не Iterator
.
Чтобы преодолеть эту часть проблемы, вы должны вызывать Iterator.remove
, а не Collection.remove
, поскольку это гарантирует, что Iterator
теперь знает о модификации Collection
. Iterator
в этом случае будет отслеживать структурную модификацию, происходящую с помощью метода remove
. Поэтому ваш код должен выглядеть следующим образом:
final Multimap<Term, BooleanClause> terms = getTerms(bq);
for (Term t : terms.keySet()) {
Collection<BooleanClause> C = new HashSet(terms.get(t));
if (!C.isEmpty()) {
for (Iterator<BooleanClause> it = C.iterator(); it.hasNext();) {
BooleanClause c = it.next();
if(c.isSomething()) it.remove(); // <-- invoke remove on the Iterator. Removes the element returned by it.next.
}
}
}