我有一个 productList
维护在一个名为的文件中 Products.java
private List<String> productList = Collections.synchronizedList(new ArrayList());
现在创建一个同步列表,将确保add / remove之类的操作将具有隐式锁定,并且我不需要显式锁定这些操作。
我有一个暴露的函数返回一个 unmodifiableList
这个清单。
public List getProductList(){
return Collections.unmodifiableList(productList);
}
在我的应用程序中,各种线程可以同时调用此函数。所以我需要放一个 synchronized
将List转换为不可修改的List时阻塞,或者由于我使用的是sychronizedList,这是否已经处理好了?
TIA。
它不需要同步,因为不可修改的列表包装同步的列表。但是,除了迭代的目的之外,在不可修改的列表上进行同步没有多大用处 要求 手动同步无论:
用户必须手动同步返回的内容
迭代时列出:
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
。
应该使用synchronized的唯一地方是当你循环它时,如下所述 的javadoc:
当迭代它时,用户必须手动同步返回的列表:
但是,一旦将其包裹在一个中,就无法做到这一点 unmodifiableList
,使返回结果不安全。在并发访问的情况下,它可能返回损坏的数据。
返回后端列表可能更好,而不是返回后端列表,因此调用不需要担心同步性能。
public List getProductList(){
synchronized (productList) {
return new ArrayList<>(productList);
}
}
最简单的解决方案是每次都获取快照,
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
请注意,此代码并不比快照解决方案简单。客户端仍然可以“错过”更新,就像快照解决方案一样。
您必须决定是否要强制客户端代码执行此类锁定。
当然,这并非没有优点;如果列表很大并且更新很少,那么它的性能可能比快照解决方案更好。
如果这种方法更适合应用程序,我们可能会设计类似的东西
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->
{
...
});