Имеет ли Collections.unmodifiableList(список) блокировку?
У меня есть productList
, хранящийся в файле с именем Products.java
private List<String> productList = Collections.synchronizedList(new ArrayList());
Теперь создание синхронизированного списка гарантирует, что операции, такие как add/remove, будут иметь неявный замок, и мне не нужно блокировать эти операции явным образом.
У меня есть функция, которая возвращает unmodifiableList
этого списка.
public List getProductList(){
return Collections.unmodifiableList(productList);
}
В моем приложении различные потоки могут одновременно вызвать эту функцию. Так что мне нужно поставить блок synchronized
при преобразовании списка в немодифицируемый список или это будет уже рассмотрено, поскольку я использую sychronizedList?
ТИА.
Ответы
Ответ 1
Его не нужно синхронизировать, так как немодифицируемый список обертывает синхронизированный. Но для синхронизации в немодифицируемом списке не так много использовать, кроме как для целей итерации, которые требуют ручной синхронизации независимо:
Обязательно, чтобы пользователь вручную синхронизировал список при его повторении:
List list = Collections.synchronizedList(new ArrayList());
...
synchronized (list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
Несоблюдение этого совета может привести к недетерминированному поведению.
EDIT: Как указывает Феррибиг, на самом деле невозможно безопасно синхронизировать с немодифицируемой оболочкой. Возможно, вы захотите рассмотреть альтернативное решение для обеспечения безопасности потоков, например CopyOnWriteArrayList
.
Ответ 2
Единственное место, где следует использовать синхронизацию, - это когда вы перебираете его, как объясняется javadoc:
Обязательно, чтобы пользователь вручную выполнял синхронизацию по возвращенному списку при итерации по нему:
Однако вы не можете сделать это, как только вы завернете его в unmodifiableList
, что делает его небезопасным для результата возврата. Он может возвращать поврежденные данные в случае одновременного доступа.
Вместо того, чтобы возвращать свой бэкэнд-лист, может быть лучше вернуть копию бэкэнд, поэтому вызов не должен беспокоиться о синхронизации производительности.
public List getProductList(){
synchronized (productList) {
return new ArrayList<>(productList);
}
}
Ответ 3
Нет необходимости устанавливать синхронизированный блок.
Ответ 4
Самое простое решение - получать снимок каждый раз,
list = get snapshot
{
work on the list
}
discard list // GC it
поскольку моментальный снимок представляет собой постоянную замороженную структуру данных, клиент может свободно обращаться к ней.
Но если клиент обращается к структуре данных, которая может быть изменена другой стороной, это становится проблематичным. Забудьте о проблемах concurrency; подумайте о семантике следующего кода
{
n = list.size();
list.get(n-1);
}
get(n-1)
может завершиться неудачно, потому что список, возможно, сократился при его вызове.
Чтобы иметь некоторую гарантию согласованности, клиентская сторона должна предоставлять явные разграничения транзакций во время сеанса доступа, например
acquire lock // preferably a read-lock
{
work on the list
}
release lock
Обратите внимание, что этот код не проще, чем решение моментального снимка. И клиент может все еще "пропустить" обновления, как решение моментального снимка.
И вам нужно решить, хотите ли вы заставить клиентские коды выполнять такую блокировку.
Это не без достоинств, конечно; это может быть лучше в производительности, чем решение моментального снимка, если список большой, а обновления нечасты.
Если этот подход более подходит для приложения, мы можем создать что-то вроде
interface CloseableList extends List, Closeable {}
public CloseableList readProducts() // acquire read-lock when called
-- client code
try{CloseableList list = readProducts())
{
....
}
// on close(), release the read-lock
Если клиенту требуется только итерация продуктов, мы можем использовать java8 Stream
final ReadWriteLock lock = ...
private ArrayList productList = new ArrayList();
// modifications to `productList` must be protected by write lock
// javadoc: reader must close the stream!
public Stream readProducts()
{
lock.readLock().lock();
return productList.stream().onClose( lock.readLock()::unlock );
}
-- client code
try(Stream products = readProducts())
{
...
}
Мы также можем разработать API для ввода кода клиента, чтобы мы могли окружить его защитой
public void readProducts(Consumer<List> action)
{
lock read
action.accept(productList);
finally unlock read
}
-- client code
readProducts(list->
{
...
});