Руководство по CopyOnWriteArrayList

Руководство по CopyOnWriteArrayList

1. обзор

В этой быстрой статье мы рассмотримCopyOnWriteArrayList из пакетаjava.util.concurrent.

Это очень полезная конструкция в многопоточных программах - когда мы хотим выполнять итерации по списку потокобезопасным способом без явной синхронизации.

2. CopyOnWriteArrayList API

В конструкцииCopyOnWriteArrayList используется интересная техника, позволяющая сделать его потокобезопасным без необходимости синхронизации. Когда мы используем любой из методов модификации, напримерadd() илиremove() –, все содержимоеCopyOnWriteArrayList копируется в новую внутреннюю копию.

Благодаря этому простому фактуwe can iterate over the list in a safe way, even when concurrent modification is happening.

Когда мы вызываем методiterator() дляCopyOnWriteArrayList,, мы возвращаемIterator, подкрепленный неизменяемым снимком содержимогоCopyOnWriteArrayList.

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

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

3. Итерация болееCopyOnWriteArrayList при вставке

Допустим, мы создаем экземплярCopyOnWriteArrayList, в котором хранятся целые числа:

CopyOnWriteArrayList numbers
  = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 8});

Затем мы хотим перебрать этот массив, поэтому мы создаем экземплярIterator:

Iterator iterator = numbers.iterator();

После созданияIterator мы добавляем новый элемент в списокnumbers:

numbers.add(10);

Имейте в виду, что когда мы создаем итератор дляCopyOnWriteArrayList,, мы получаем неизменяемый снимок данных в списке в момент вызоваiterator().

Из-за этого во время итерации мы не увидим число10 в итерации:

List result = new LinkedList<>();
iterator.forEachRemaining(result::add);

assertThat(result).containsOnly(1, 3, 5, 8);

Последующая итерация с использованием вновь созданногоIterator также вернет добавленное число 10:

Iterator iterator2 = numbers.iterator();
List result2 = new LinkedList<>();
iterator2.forEachRemaining(result2::add);

assertThat(result2).containsOnly(1, 3, 5, 8, 10);

4. Удаление при повторении недопустимо

CopyOnWriteArrayList был создан для обеспечения возможности безопасного перебора элементов даже при изменении базового списка.

Из-за механизма копирования операцияremove() для возвращенногоIterator не разрешена, что приводит кUnsupportedOperationException:

@Test(expected = UnsupportedOperationException.class)
public void whenIterateOverItAndTryToRemoveElement_thenShouldThrowException() {

    CopyOnWriteArrayList numbers
      = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 8});

    Iterator iterator = numbers.iterator();
    while (iterator.hasNext()) {
        iterator.remove();
    }
}

5. Заключение

В этом кратком руководстве мы рассмотрели реализациюCopyOnWriteArrayList из пакетаjava.util.concurrent.

Мы увидели интересную семантику этого списка и то, как его можно итерировать потокобезопасным способом, в то время как другие потоки могут продолжать вставлять или удалять из него элементы.

Реализация всех этих примеров и фрагментов кода можно найти вGitHub project - это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.