Перечисление 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, как он предназначен.

Ответ 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