Имеет ли 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->
{
    ...
});