Collections.synchronizedXXX
(例如synchronizedSet()
)时,我们会得到底层集合的同步“视图”。然而,这些包装方法的文档声明,在使用迭代器迭代集合时,我们必须明确地对集合进行同步。
您要选择哪个选项来解决这个问题?
我只能看到以下方法:
- 按照文档所述执行:在集合上进行同步
- 在调用
iterator()
之前克隆集合 - 使用具有线程安全迭代器的集合(我只知道
CopyOnWriteArrayList
/Set)
Collections.synchronizedXXX
(例如synchronizedSet()
)时,我们会得到底层集合的同步“视图”。iterator()
之前克隆集合CopyOnWriteArrayList
/Set)你已经回答了你的奖励问题:不,使用增强型for循环并不安全,因为它使用迭代器。
至于哪种方法最适合-这真的取决于你的背景:
CopyOnWriteArrayList
可能是最合适的。根据您的访问模式而定。如果您的并发较低且写入频繁,则1具有最佳性能。如果您的并发较高且写入不频繁,则3具有最佳性能。在几乎所有情况下,选项2的性能都很差。
foreach
调用 iterator()
,因此完全适用相同的原则。
您可以使用Java 5.0中新增的一种较新的集合,支持在迭代时进行并发访问。另一种方法是使用toArray进行复制,这是线程安全的(在复制期间)。
Collection<String> words = ...
// enhanced for loop over an array.
for(String word: words.toArray(new String[0])) {
}
我建议放弃使用 Collections.synchronizedXXX
,并在客户端代码中统一处理所有锁定。基本集合不支持线程化代码中有用的复合操作,即使您使用 java.util.concurrent.*
,代码也更加困难。我建议尽可能保持代码与线程无关。将困难且容易出错的线程安全(如果我们非常幸运)代码最小化。
你的三个选项都可以运作。选择合适的选项取决于你的具体情况。
如果你需要一个列表实现,而且不介意每次写入时复制底层存储,那么CopyOnWriteArrayList
就可以胜任。只要你没有非常大的集合,这样的性能表现还是相当不错的。
如果你需要Map
或Set
接口,那么ConcurrentHashMap
或者使用Collections.newSetFromMap
创建的"ConcurrentHashSet
"就可以胜任。显然通过这种方式不能进行随机访问。这两种的优点之一在于,它们可以很好地处理大型数据集-当它们发生变化时,它们只会复制底层数据存储的一小部分。
这个问题有点老了(抱歉,我有点晚了..),但我仍然想要添加我的答案。
我会选择你的第二个选择(即在调用iterator()之前克隆集合),但有一个重大的转折。
假设你想使用iterator进行迭代,你不必在调用.iterator()之前复制Collection,并且有点否定了迭代器模式的思想,而是可以编写一个“ThreadSafeIterator”。
它将在相同的前提下工作,复制Collection,但不让迭代类知道你刚刚这样做了。这样的迭代器可能看起来像这样:
class ThreadSafeIterator<T> implements Iterator<T> {
private final Queue<T> clients;
private T currentElement;
private final Collection<T> source;
AsynchronousIterator(final Collection<T> collection) {
clients = new LinkedList<>(collection);
this.source = collection;
}
@Override
public boolean hasNext() {
return clients.peek() != null;
}
@Override
public T next() {
currentElement = clients.poll();
return currentElement;
}
@Override
public void remove() {
synchronized(source) {
source.remove(currentElement);
}
}
}
Semaphore
类来确保线程安全性等。但是请对删除方法持保留态度。这取决于需要实现克隆/复制/toArray()等操作的结果,new ArrayList(..)等方法可以获得快照并且不会锁定集合。 使用synchronized(collection)和迭代确保在迭代结束时没有修改,即有效地锁定它。
顺便提一下:(通常情况下,toArray()是首选,除非内部需要创建临时ArrayList)。此外,请注意,除了toArray()之外的任何操作都应该包装在synchronized(collection)中,前提是使用Collections.synchronizedXXX。