Коллекции. Синхронизированный список и синхронизация
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
, это приведет к одновременной проблеме.