Это прямо сейчас ответит, как CopyOnWriteArrayList устраняет необходимость исключения ConcurrentModificationException.
Когда вы изменяете коллекцию, CopyOnWriteArrayList делает две вещи
- Запрещает другим потокам изменять коллекцию посредством блокировки
- Копирует все элементы в текущем 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.