Зачем мне нужно синхронизировать список, возвращенный Collections.synchronizedList

Я нашел это на dos.oracle.com

public static Список synchronizedList (Список списка)

Возвращает синхронизированный (потокобезопасный) список, поддерживаемый указанным список. Чтобы гарантировать последовательный доступ, важно, чтобы все доступ к списку поддержки выполняется через возвращенный список. Крайне важно, чтобы пользователь вручную синхронизировал список при его повторении:

  List list = Collections.synchronizedList(new ArrayList());
      ...
  synchronized(list) {
      Iterator i = list.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
  }

Мой вопрос: зачем мне синхронизировать список для его итерации, если Collections.synchronizedList(); должен возвращать уже синхронизированный список?

Я просто присоединяюсь к списку в двух потоках: Один поток просто добавляет, а другой поток - для получения и удаления. Какие другие классы вы рекомендуете использовать для этого сценария?

Спасибо за чтение.

Ответ 1

Синхронизированный список означает, что операции add, remove и т.д. синхронизированы и поэтому являются атомарными. Итерация, однако, отсутствует, и если поток adds, а другой - итерация, вы можете получить исключение ConcurrentModificationException.

Ручная синхронизация вашего итерационного блока гарантирует, что список не будет изменен во время итерации.

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

Ответ 2

Сначала я был немного смущен этой темой, потому что большинство примеров, которые я нашел, были без контекста. Я наконец нашел это сообщение в блоге, которое очистило меня для меня: http://netjs.blogspot.de/2015/09/how-and-why-to-synchronize-arraylist-in-java.html

В приведенном выше примере похоже, что мне просто нужно преобразовать свой список в Collections.synchronizeList(), а затем я могу добавлять и удалять элементы, не беспокоясь о безопасности потоков. Но здесь важно отметить, что вам нужно синхронизировать список, прежде чем он будет передан в разные потоки, поскольку в противном случае доступ к спискам не будет взаимоисключающим.

Таким образом, полный пример:

public class SynchroProblem implements Runnable{
  private List<Integer> myList;

  //Constructor
  public SynchroProblem(List<Integer> myList){
    this.myList = myList;
  }

  @Override
  public void run() {
    // Do stuff with the list .add(), .remove(), ...
    myList.add(5);

    // Even if mylist is synchronized the iterator is not, 
    // so for using the iterator we need the synchronized block
    synchronized (myList){
      // do stuff with iterator e.g.
      Iterator<Integer> iterator = myList.iterator();
      while (iterator.hasNext()){
        int number = iterator.next();
        if (number == 123){
          iterator.remove();
        }
      }

    }
  }

  public static void main(String[] args) {

    List<Integer> originalList = new ArrayList<Integer>();

    // Synchronize list
    List<Integer> syncList = Collections.synchronizedList(originalList);

    // Create threads and pass the synchronized list
    Thread t1 = new Thread(new SynchroProblem(syncList));
    Thread t2 = new Thread(new SynchroProblem(syncList));

    t1.start();
    t2.start();

  }
}