Как исключение параллельной модификации обрабатывается внутренне CopyOnWriteArrayList / ConcurrentHashMap? - PullRequest
3 голосов
/ 22 апреля 2019

Я хочу понять изнутри, как исключение одновременной модификации обрабатывается в параллельных коллекциях, таких как ConcurrentHashMap и CopyOnWriteArrayList.

В Интернете так много блогов, которые предлагают использовать эти две структуры данных, чтобы избежатьисключение одновременной модификации.Но ничто не объясняет, как это исключение внутренне обрабатывается одновременным сбором.Мне нужно подробное объяснение.

Ответы [ 3 ]

3 голосов
/ 22 апреля 2019

Дословный ответ на ваш вопрос не очень интересен.ConcurrentHashMap и CopyOnWriteArrayList не выбрасывают ConcurrentModificationException, потому что они не содержат код для его выброса.

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

Классы, которые поддерживают безопасную параллельную модификацию, не генерируют ConcurrentModificationException потому что им это не нужно.

Если вы пытаетесь отладить ConcurrentModificationException, существует множество других вопросов, которые помогут ответить на этот вопрос:

1 голос
/ 22 апреля 2019

Это прямо сейчас ответит, как CopyOnWriteArrayList устраняет необходимость исключения ConcurrentModificationException.

Когда вы изменяете коллекцию, CopyOnWriteArrayList делает две вещи

  1. Запрещает другим потокам изменять коллекцию посредством блокировки
  2. Копирует все элементы в текущем CopyOnWriteArrayList вновый массив и затем назначает этот новый массив экземпляру массива класса

Так как же это предотвратить CME?CME в стандартных коллекциях будет выброшен только в результате итерации.Исключение выдается, если во время итерации по коллекции выполняется добавление или удаление в одном и том же экземпляре коллекции.

Итератор CopyOnWriteArrayList назначает текущий массив как конечное поле снимок коллекции и использует ее для итерации.Если другой поток (или даже тот же поток) пытается добавить его в CopyOnWriteArrayList, тогда обновления будут применяться к новой копии, а не к снимку , который мы в настоящее время повторяем.

Например, мы знаем, что метод add выглядит как

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

Обратите внимание, что выполняется локальное назначение потока newElements, когда оно будет завершено, он будет установлен в экземпляр класса volatile array.

Затем следует итератор, он определяется как

static final class COWIterator<E> implements ListIterator<E> {
    /** Snapshot of the array */
    private final Object[] snapshot;
    /** Index of element to be returned by subsequent call to next.  */
    private int cursor;

Так что при итерации мы читаем любой массив перед любыми модификациями, и поскольку никакой другой поток не может изменить снимок, который мы смотрим наНе может произойти исключение ConcurrentModificationException.

1 голос
/ 22 апреля 2019

Вот определение add() метода ArrayList и CopyOnWriteArrayList.

ArrayList:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

CopyOnWriteArrayList:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

Из приведенного выше кода ясно, что CopyOnWriteArrayList захватывает блокировку перед изменением карты. Здесь я только что опубликовал код метода add. Если вы посмотрите на код remove() / addAll() или любой method which modifies List structurally, вы увидите, что перед изменением коллекции он блокируется. Также метод итератора ArrayList, такой как next()/remove(), проверяет наличие изменений, но метод итератора CopyOnWriteArrayList не проверяет наличие изменений. Например:

Метод итератора ArrayList next ():

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

Метод CopyOnWriteArrayList итератор next ():

    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
...