2017-02-02 1 views
1

Я знаю, что такой кодЕсть ли способ выбрать «неуказанное поведение», а не ConcurrentModificationException?

for (Object o: collection){ 
    if (condition(i)){ 
     collection.remove(i); 
    } 
} 

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

for (Object o: set){// set is an instance of java.util.LinkedHashSet 
    if (condition(o)){ 
     set.remove(other(o)); 
    } 
} 

Где другой (о) гарантированно будет «далеко» от о в упорядочении множества. В моей конкретной реализации он никогда не будет меньше 47 шагов «от». Кроме того, если условие (o) истинно, то цикл, о котором идет речь, будет гарантировать короткое замыкание до того, как он достигнет места, где находился другой (o). Таким образом, вся часть набора, к которому обращается итератор, полностью отделена от модифицированной части. Кроме того, особенно сильные стороны LinkedHashSet (быстрая вставка и удаление случайного доступа, гарантированный порядок итераций) кажутся особенно подходящими для этой точной операции.

Я полагаю, что мой вопрос двоякий: во-первых, такая операция все еще опасна с учетом вышеуказанных ограничений? Единственный способ, которым я могу думать, это то, что значения Iterator предварительно загружены заранее и кэшированы, что, я полагаю, улучшит производительность во многих приложениях, но похоже, что это также уменьшит его во многих других, и, следовательно, будет странный выбор для универсального класса от java.util. Но, возможно, я ошибаюсь. Когда дело доходит до таких вещей, как кеширование, моя интуиция об эффективности часто бывает подозрительной. Во-вторых, если предположить, что такая вещь, по крайней мере теоретически, безопасна, есть ли способ, не до конца полностью внедряющий LinkedHashSet или жертвуя эффективностью для достижения этой операции? Могу ли я передать Коллекции, чтобы игнорировать тот факт, что я изменяю другую часть набора, и просто продолжаю свой бизнес, как обычно? Моя текущая работа заключается в том, чтобы сначала добавить элементы в промежуточную коллекцию, а затем добавить их в основной набор после завершения цикла, но это неэффективно, так как он должен дважды добавлять значения.

+0

Если вы хотите неуказанное поведение, ConcurrentModificationException считает это. – user2357112

+0

В любом случае, нет, вы не можете сообщить сборке пропустить параллельные проверки модификации. – user2357112

+0

Справедливо, но я думаю, что мой вопрос достаточно ясен относительно того, что я ищу. Документация для ConcurrentModificationException гласит: «... Итераторы, которые делают это, называются отказоустойчивыми итераторами, поскольку они терпят неудачу быстро и чисто, а скорее рискуют произвольным, недетерминированным поведением в неопределенное время в будущем ...». Я хочу, чтобы это рисковало, потому что в этом конкретном случае такое недетерминированное поведение кажется маловероятным. В качестве альтернативы, если есть достаточно простой и сравнительно эффективный способ переписать код, я так же счастлив сделать это. –

ответ

2

Выбрасывается ConcurrentModificationException, потому что ваша коллекция не сможет обрабатывать удаление (или добавление) в любое время. Например, что, если удаление, которое вы выполнили, означало, что ваш LinkedHashSet должен был уменьшить/увеличить пространство, лежащее под капотом, лежащее ниже HashMap? Это должно было бы внести много изменений, что, возможно, сделало бы итератор бесполезным.

У вас есть два варианта:

Используйте Iterator для перебирать элементы и удалить их, например, вызывая Iterator iter = linkedHashSet.iterator(), чтобы получить итератор, а затем удалить элементы с помощью iter.remove()

Используйте одну из параллельных коллекций, доступных в рамках java.util.concurrent пакета, которые предназначены для обеспечения параллельных модификаций

This question содержит интересные подробности об использовании Iterator

UPDATE после комментариев:

Вы можете использовать следующую схему, чтобы удалить элементы, которые вы хотите, без используя ConcurrentModificationException: соберите элементы, которые вы хотите удалить, в List, пробираясь через элементы LinkedHashSet.Затем проведите через каждый элемент toBeDeleted в списке и удалите его из LinkedHashSet.

+0

Дело в LinkedHashSet заключается в том, что итерация обрабатывается связанным списком. Даже если бы HashSet пришлось перестроить, изменение связанного списка все равно было бы очень локализовано и далеко от моей модификации, поэтому итератор не должен быть затронут. –

+1

Да, но то, что вы говорите, означает, что 'LinkedHashSet' должен будет отслеживать все активные итераторы и проверять каждую модификацию, является ли она _close_ или _far_ от _all_ активных активных итераторов. Поскольку эти изменения могут происходить в других потоках, это означало бы, что для выполнения этой проверки также потребуется выполнить некоторую синхронизацию. – JChrist

+0

Вы _guessing_, что это «далеко от вашей модификации». Если 'LinkedHashSet' может поддерживать эту операцию, это не будет бросать' ConcurrentModificationException'. Это вариант 'LinkedHashSet' _chose_, потому что его дизайнеры знали, что он не может его поддерживать. –

Смежные вопросы