Существует ли потокобезопасный ArrayList в Java?

«Существует ли потокобезопасный ArrayList в Java?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Стандартный ArrayList в Java не является потокобезопасным. Его использование из нескольких потоков без синхронизации приводит к неопределённому поведению и повреждению данных.

Потокобезопасные альтернативы:

  1. CopyOnWriteArrayList (из java.util.concurrent)

    • Принцип: При любой модификации (добавление, удаление) создаётся новая копия внутреннего массива. Итераторы работают с неизменяемым снимком (snapshot) на момент создания.
    • Лучше всего подходит для сценариев с частым чтением и очень редкой записью.
    • Пример:

      import java.util.concurrent.CopyOnWriteArrayList;
      
      CopyOnWriteArrayList<String> safeList = new CopyOnWriteArrayList<>();
      safeList.add("Data");
      // Итерация безопасна, даже если другой поток модифицирует список
      for (String item : safeList) {
          System.out.println(item);
      }
  2. Collections.synchronizedList(new ArrayList<>())

    • Принцип: Возвращает обёртку, которая синхронизирует все методы исходного списка на одном мьютексе.
    • Гарантирует атомарность отдельных операций (например, add), но для составных операций (например, проверка contains, а затем add) требуется внешняя синхронизация.
    • Пример:
      List<String> syncList = Collections.synchronizedList(new ArrayList<>());
      // Для итерации всё равно требуется ручная синхронизация
      synchronized(syncList) {
          for (String s : syncList) {
              // ...
          }
      }

Выбор зависит от паттерна доступа:

  • CopyOnWriteArrayList — для read-heavy workloads.
  • synchronizedList — когда важна актуальность данных при записи, а операции чтения и записи сравнимы по частоте.