Перечисление Java против Iterator
Enumeration
не бросает ConcurrentModificationException
, почему?
См. ниже код.
public static void main(String[] args) {
Vector<String> v=new Vector<String>();
v.add("Amit");
v.add("Raj");
v.add("Pathak");
v.add("Sumit");
v.add("Aron");
v.add("Trek");
Enumeration<String> en=v.elements();
while(en.hasMoreElements())
{
String value=(String) en.nextElement();
System.out.println(value);
v.remove(value);
}
}
Он печатает только:
Amit
Pathak
Aron
Почему такое поведение. Можем ли мы сказать, что Enumerator
является потокобезопасным.
Изменить: При работе с Iterator он бросает ConcurrentModificationException
в однопоточное приложение.
public static void main(String[] args) {
Vector<String> v=new Vector<String>();
v.add("Amit");
v.add("Raj");
v.add("Pathak");
v.add("Sumit");
v.add("Aron");
v.add("Trek");
Iterator<String> it=v.iterator();
while(it.hasNext())
{
String value=(String) it.next();
System.out.println(value);
v.remove(value);
}
}
Пожалуйста, проверьте.
Ответы
Ответ 1
Перечисление не бросает ConcurrentModificationException, почему?
Потому что в вызываемом коде нет пути, который выдает это исключение. Изменить: я имею в виду реализацию, предоставляемую классом Vector, а не интерфейс Enumeration вообще.
Почему такое поведение. Можем ли мы сказать, что Enumerator является потокобезопасным.
Он потокобезопасен в некотором смысле, что выполненный код правильно синхронизирован. Тем не менее, я не думаю, что результат, полученный вашим циклом, будет тем, что вы могли бы исключить.
Причиной вашего вывода является то, что объект Enumeration поддерживает счетчик, который увеличивается после каждой активации nextElement()
. Этот счетчик не знает о вашей активации remove()
.
Ответ 2
Обратите внимание, что ConcurrentModificationException не имеет ничего общего с concurrency в смысле многопоточности или безопасности потоков. Некоторые коллекции допускают параллельные модификации, некоторые - нет. Обычно вы можете найти ответ в документах. Но одновременное не означает одновременно разные потоки. Это означает, что вы можете изменить коллекцию во время итерации.
ConcurrentHashMap - это особый случай, поскольку он явно определен как потокобезопасный и редактируемый при повторении (что, на мой взгляд, верно для всех поточно-безопасных коллекций).
В любом случае, пока вы используете один поток для итерации и изменения коллекции, ConcurrentHashMap является неправильным решением вашей проблемы. Вы неправильно используете API. Вы должны использовать Iterator.remove() для удаления элементов. В качестве альтернативы вы можете сделать копию коллекции, прежде чем выполнять итерацию и изменение оригинала.
EDIT:
Я не знаю ни одного Перечисления, которое генерирует исключение ConcurrentModificationException. Однако поведение в случае одновременной модификации может быть не таким, как вы ожидаете. Как вы видите в этом примере, перечисление пропускает каждый второй элемент в списке. Это связано с тем, что внутренний индекс увеличивается независимо от удалений. Так вот что происходит:
- en.nextElement() - возвращает первый элемент из Vector, увеличивает индекс на 1
- v.remove(value) - удаляет первый элемент из Vector, сдвигает все оставшиеся элементы
- en.nextElement() - возвращает второй элемент из Vector, который теперь является "Pathak"
Неудачное поведение Iterator защищает вас от такого рода вещей, поэтому обычно предпочтительнее Enumment. Вместо этого вы должны сделать следующее:
Iterator<String> it=v.iterator();
while(it.hasNext())
{
String value=(String) it.next();
System.out.println(value);
it.remove(); // not v.remove(value); !!
}
В качестве альтернативы:
for(String value : new Vector<String>(v)) // make a copy
{
String value=(String) it.next();
System.out.println(value);
v.remove(value);
}
Первое, безусловно, предпочтительнее, так как вам действительно не нужна копия, если вы используете API, как он предназначен.
Ответ 3
отказоустойчивое поведение, которое вызывает исключение ConcurrentModificationException, реализуется только для Iterator, как вы можете читать
http://docs.oracle.com/javase/6/docs/api/java/util/Vector.html
Ответ 4
Параллельная модификация здесь не имеет ничего общего с потоками.
Concurrency здесь просто означает, что вы изменяете коллекцию во время итерации по ней. (В вашем примере это происходит в том же потоке.)
Итераторы и перечисления коллекций могут бросать ConcurrentModificationException
в этом случае, но не обязательно. Те, которые действительно демонстрируют неудачное поведение. По-видимому, перечисление Vector
не обязательно быстро.
Потоковая безопасность, очевидно, включает несколько потоков. Vector
является потокобезопасным только в том смысле, что его операции (например, add, get и т.д.) синхронизируются. Это делается для того, чтобы избежать детерминированного поведения, когда один поток добавляет элемент, в то время как другой поток пытается удалить его, например.
Теперь, когда один поток структурно изменяет вашу коллекцию, в то время как другой поток выполняет итерацию по ней, вы должны иметь дело с проблемами безопасности потоков и одновременной модификации. В этом случае и, возможно, в целом, безопаснее не полагаться на ConcurrentModificationException
. Лучше всего выбрать соответствующую реализацию коллекции (например, поточно-безопасную) и самостоятельно избегать/запрещать одновременную модификацию.
Некоторые итераторы позволяют добавлять/устанавливать/удалять элементы через сам итератор. Это может быть хорошей альтернативой, если вам действительно нужна параллельная модификация.
Ответ 5
Короткий ответ: Это a bonus feature
, который был изобретен после того, как перечисление уже было там, поэтому тот факт, что перечислитель не бросает его, не предлагает ничего конкретного.
Длинный ответ:
Из Википедии:
Реализации коллекции в пред-JDK 1.2 [...] не содержали коллекций. Стандартные методы группировки Java объекты были через массивы, классы Vector и Hashtable, которые, к сожалению, нелегко расширить, и не стандартный членский интерфейс. Чтобы устранить необходимость повторного использования сбор данных [...] рамки коллекций были разработаны и разработаны в основном Джошуа Блох, и был представлен в JDK 1.2.
Когда команда Bloch сделала это, они подумали, что было бы неплохо добавить новый механизм, который отправляет сигнал тревоги (ConcurrentModificationException
), когда их коллекции не были правильно синхронизированы в многопоточной программе. Есть две важные вещи, которые следует отметить в отношении этого механизма: 1) он не гарантированно ловить ошибки concurrency - исключение бросается, если вам повезет. 2) Исключение также вызывается, если вы неправильно используете коллекцию с использованием одного потока (как в вашем примере).
Таким образом, коллекция, не выбрасывающая ConcurrentModificationException
при обращении к нескольким потокам, не подразумевает, что она также является потокобезопасной.
Ответ 6
Это зависит от того, как вы получаете Перечисление. См. Следующий пример: он бросает ConcurrentModificationException
:
import java.util.*;
public class ConcurrencyTest {
public static void main(String[] args) {
Vector<String> v=new Vector<String>();
v.add("Amit");
v.add("Raj");
v.add("Pathak");
v.add("Sumit");
v.add("Aron");
v.add("Trek");
Enumeration<String> en=Collections.enumeration(v);//v.elements();
while(en.hasMoreElements())
{
String value=(String) en.nextElement();
System.out.println(value);
v.remove(value);
}
System.out.println("************************************");
Iterator<String> iter = v.iterator();
while(iter.hasNext()){
System.out.println(iter.next());
iter.remove();
System.out.println(v.size());
}
}
}
Перечисление - это просто интерфейс, его фактическое поведение зависит от реализации. Перечисление из вызова Collections.enumeration() обертывает итератор каким-то образом, поэтому он действительно быстро работает, но перечисление, полученное от вызова Vector.elements(), не является.
Непросроченная перечисление может ввести произвольное, недетерминированное поведение в неопределенное время в будущем. Например: если вы напишете основной метод как это, он будет вызывать java.util.NoSuchElementException после первой итерации.
public static void main(String[] args) {
Vector<String> v=new Vector<String>();
v.add("Amit");
v.add("Raj");
v.add("Pathak");
Enumeration<String> en = v.elements(); //Collections.enumeration(v);
while(en.hasMoreElements())
{
v.remove(0);
String value=(String) en.nextElement();
System.out.println(value);
}
}
Ответ 7
удалите v.remove(value)
, и все будет работать как ожидалось
Изменить: извините, неправильно прочитайте вопрос там
Это не имеет ничего общего с потоками. Вы даже не многопоточны, поэтому нет причин, по которым Java будет генерировать исключение для этого.
Если вы хотите исключений, когда вы меняете вектор, сделайте его Unmodifiable