Collections.unmodifiableList(list)是否需要锁定?

时间:2016-02-09 08:51:56

标签: java multithreading arraylist collections

我在名为productList

的文件中维护了Products.java
private List<String> productList = Collections.synchronizedList(new ArrayList());

现在创建一个同步列表,将确保添加/删除等操作具有隐式锁定,并且我不需要显式锁定这些操作。

我有一个暴露的函数,它返回此列表的unmodifiableList

public List getProductList(){

 return Collections.unmodifiableList(productList);
}

在我的应用程序中,各种线程可以同时调用此函数。因此,在将List转换为不可修改的List时,我需要放置一个synchronized块,或者由于我使用的是sychronizedList,这是否已经处理好了?

TIA。

4 个答案:

答案 0 :(得分:5)

它不需要同步,因为不可修改的列表包含同步的列表。但是,除了迭代的目的之外,在不可修改的列表上进行同步并没有多大用处,requires手动同步无论如何:

  

用户必须手动同步返回的内容   迭代时列出:

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

不遵循此建议可能会导致非确定性行为。

编辑:正如Ferrybig指出的那样,实际上不可能与不可修改的包装器安全地同步。您可能需要考虑替代的线程安全解决方案,例如CopyOnWriteArrayList

答案 1 :(得分:4)

应该使用synchronized的唯一地方是当你循环它时,如javadoc所解释的那样:

  

当迭代时,用户必须手动同步返回的列表:

但是,一旦将其包裹在unmodifiableList中,就无法执行此操作,从而使返回结果不安全。在并发访问的情况下,它可能会返回损坏的数据。

而不是返回后端列表,最好返回后端的副本,因此调用不需要担心同步性能。

public List getProductList(){
    synchronized (productList) {
       return new ArrayList<>(productList);
    }
}

答案 2 :(得分:0)

无需放置同步块。

答案 3 :(得分:0)

最简单的解决方案是每次都获取快照,

list = get snapshot
{
    work on the list
}
discard list // GC it

由于快照是一个不变的冻结数据结构,客户端可以自由访问它。

但是如果客户端访问可能被另一方修改的数据结构,则会出现问题。忘掉并发问题;想一想下面代码的语义

{
    n = list.size();
    list.get(n-1);
}

get(n-1)可能会失败,因为列表在被调用时可能会缩小。

要获得某种一致性保证,客户端必须在访问会话期间提供明确的事务划分,例如

acquire lock // preferably a read-lock
{
    work on the list
}
release lock

请注意,此代码并不比快照解决方案简单。客户仍然会错过&#34;更新,就像快照解决方案一样。

您必须决定是否要强制客户端代码执行此类锁定。

当然,并非没有优点;如果列表很大并且更新很少,那么它的性能可能比快照解决方案更好。

如果这种方法更适合应用程序,我们可能会设计类似

的方法
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->
{
    ...
});