Коллекции. Синхронизированный список и синхронизация

List<String> list = Collections.synchronizedList(new ArrayList<String>());
synchronized (list) {
    list.add("message");
}

Действительно ли нужен блок "synchronized (list) {}"?

Ответ 1

Вам не нужно синхронизировать, как вы помещаете в свой пример. ОДНАКО, очень важно, вам нужно синхронизировать список, когда вы его повторяете (как указано в Javadoc):

Обязательно, чтобы пользователь вручную синхронизировал список при его повторении:

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

Ответ 2

Это зависит от точного содержимого блока synchronized:

  • Если блок выполняет одиночную, атомную операцию в списке (как в вашем примере), synchronized является излишним.

  • Если блок выполняет несколько операций в списке - и должен поддерживать блокировку на протяжении сложной операции - тогда synchronized не лишний. Один из распространенных примеров этого - итерирование по списку.

Ответ 3

Исходный код для метода Collections.synchronizedList add:

public void add(int index, E element) {
    synchronized (mutex) {list.add(index, element);}
}

Итак, в вашем примере не требуется добавлять синхронизацию.

Ответ 4

Также важно отметить, что любые методы, которые используют Итераторы, например Collections.sort(), также должны быть инкапсулированы внутри синхронизированного блока.

Ответ 5

Прочтите этот Oracle Doc

В нем говорится: "Обязательно, чтобы пользователь вручную синхронизировал в возвращенном списке при итерации по нему"

Ответ 6

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

Согласно JCIP, общие действия соединения могут быть

  • итерация
  • навигация
  • пут-если-отсутствует
  • проверить-то-акт

Блок синхронизированного кода OP не является составным действием, поэтому нет никакой разницы, добавьте его или нет.

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

Существует два метода, которые работают в одной коллекции list, завернутой в Collections.synchronizedList

public Object getLast(List<String> list){
    int lastIndex = list.size() - 1;
    return list.get(lastIndex);
}

public void deleteLast(List<String> list){
    int lastIndex = list.size() - 1;
    list.remove(lastIndex);
}

Если методы getLast и deleteLast вызываются одновременно двумя разными потоками, ниже могут произойти чередования, а getLast будет бросать ArrayIndexOutOfBoundsException. Предположим, что ток lastIndex равен 10.

Тема A (deleteLast) → удалить
Тема B (getLast) -------------------- > get

Поток A remove элемент перед операцией get в потоке B. Таким образом, поток B по-прежнему использует 10 как метод lastIndex для вызова метода list.get, это приведет к одновременной проблеме.