我在名为productList
Products.java
private List<String> productList = Collections.synchronizedList(new ArrayList());
现在创建一个同步列表,将确保添加/删除等操作具有隐式锁定,并且我不需要显式锁定这些操作。
我有一个暴露的函数,它返回此列表的unmodifiableList
。
public List getProductList(){
return Collections.unmodifiableList(productList);
}
在我的应用程序中,各种线程可以同时调用此函数。因此,在将List转换为不可修改的List时,我需要放置一个synchronized
块,或者由于我使用的是sychronizedList,这是否已经处理好了?
TIA。
答案 0 :(得分:5)
它不需要同步,因为不可修改的列表包含同步的列表。但是,除了迭代的目的之外,在不可修改的列表上进行同步并没有多大用处,requires手动同步无论如何:
编辑:正如Ferrybig指出的那样,实际上不可能与不可修改的包装器安全地同步。您可能需要考虑替代的线程安全解决方案,例如用户必须手动同步返回的内容 迭代时列出:
List list = Collections.synchronizedList(new ArrayList()); ... synchronized (list) { Iterator i = list.iterator(); // Must be in synchronized block while (i.hasNext()) foo(i.next()); }
不遵循此建议可能会导致非确定性行为。
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->
{
...
});