Iterator
для класса HashSet
- это отказоустойчивый итератор. Из документации HashSet
класс:
Итераторы, возвращаемые методом итератора этого класса, работают быстро:
если набор изменяется в любое время после создания итератора, в
любым способом, кроме как через собственный метод удаления итератора, итератор
генерирует исключение ConcurrentModificationException. Таким образом, перед лицом
одновременная модификация, итератор быстро и чисто дает сбой,
вместо того, чтобы рисковать произвольным, недетерминированным поведением в
неопределенное время в будущем.
Обратите внимание, что отказоустойчивое поведение итератора не может быть гарантировано
поскольку вообще невозможно сделать какие-либо жесткие гарантии
при наличии несинхронизированной параллельной модификации. Нормально-быстро
итераторы создают исключение ConcurrentModificationException
основа. Поэтому было бы неправильно писать программу, которая зависела
на этом исключении для его правильности: отказоустойчивое поведение
итераторы должны использоваться только для обнаружения ошибок.
Обратите внимание на последнее предложение - тот факт, что вы ловите ConcurrentModificationException
, подразумевает, что другой поток модифицирует коллекцию. На той же странице API Javadoc также указано:
Если несколько потоков одновременно получают доступ к хэшу, и хотя бы один
из потоков изменяет набор, он должен быть синхронизирован извне.
Обычно это достигается путем синхронизации на некотором объекте, который
естественно инкапсулирует набор. Если такого объекта не существует, множество
должен быть "обернут", используя метод Collections.synchronizedSet . это
лучше всего делать во время создания, чтобы предотвратить случайный несинхронизированный
доступ к набору:
Set s = Collections.synchronizedSet(new HashSet(...));
Я считаю, что ссылки на Javadoc говорят сами за себя в том, что должно быть сделано дальше.
Кроме того, в вашем случае я не понимаю, почему вы не используете ImmutableSet
вместо создания HashSet для объекта terms
(который может быть изменен в промежуточный период; Я не вижу реализации метода getTerms
, но у меня есть догадка, что базовый набор ключей изменяется). Создание неизменяемого набора позволит текущему потоку иметь собственную защитную копию исходного набора ключей.
Обратите внимание, что хотя ConcurrentModificationException
можно предотвратить с помощью синхронизированного набора (как указано в документации по API Java), необходимо, чтобы все потоки обращались к синхронизированной коллекции, а не к резервной коллекции напрямую (что может быть неверно в вашем случае, поскольку HashSet
, вероятно, создается в одном потоке, в то время как базовая коллекция для MultiMap
модифицируется другими потоками). Классы синхронизированной коллекции фактически поддерживают внутренний мьютекс, к которому потоки могут получить доступ; поскольку вы не можете получить доступ к мьютексу напрямую из других потоков (и это было бы довольно нелепо здесь), вам следует рассмотреть возможность использования защитной копии либо набора ключей, либо самой MultiMap с использованием метода unmodifiableMultimap
MultiMaps
класса (вам нужно вернуть немодифицируемую MultiMap из метода getTerms). Вы также можете исследовать необходимость возврата синхронизированного 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) KeyIterator
(внутренний внутренний класс HashMap
класса, который используется HashSet
) используется для итерации по Collection
.Особенностью этого Iterator
является то, что он отслеживает количество структурных изменений, выполненных на Collection
(в данном случае HashMap
) с помощью его метода Iterator.remove
.
Когда вы вызываете remove
на Collection
напрямую, а затем с последующим вызовом Iterator.next
, итератор выдает 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.
}
}
}