我们都知道在使用Collections.synchronizedXXX
时(例如synchronizedSet()
),我们会获得基础集合的同步“视图”。
但是,这些包装器生成方法的文档指出,在使用迭代器迭代集合时,我们必须在集合上显式同步。
您选择哪个选项来解决此问题?
我只能看到以下方法:
iterator()
CopyOnWriteArrayList
/ Set)作为一个额外的问题:当使用同步视图时 - 是使用foreach / Iterable线程安全吗?
答案 0 :(得分:25)
你已经回答了你的红利问题:不,使用增强的for循环不是安全 - 因为它使用了迭代器。
至于哪种方法最合适 - 这实际上取决于你的背景:
CopyOnWriteArrayList
可能是最合适的。答案 1 :(得分:6)
取决于您的访问模式。如果您具有较低的并发性和频繁写入,则1将具有最佳性能。如果您具有高并发和不频繁的写入,则3将具有最佳性能。选项2几乎在所有情况下都会表现不佳。
foreach
调用iterator()
,因此完全相同的事情适用。
答案 2 :(得分:4)
您可以使用Java 5.0中添加的一个较新的集合,它在迭代时支持并发访问。另一种方法是使用toArray复制一个线程安全的副本(在复制期间)。
Collection<String> words = ...
// enhanced for loop over an array.
for(String word: words.toArray(new String[0])) {
}
答案 3 :(得分:1)
我可能完全不满足您的要求,但如果您不了解它们,请查看google-collections并考虑“不可变性”。
答案 4 :(得分:1)
我建议删除Collections.synchronizedXXX
并在客户端代码中统一处理所有锁定。基本集合不支持在线程代码中有用的复合操作,即使使用java.util.concurrent.*
代码也比较困难。我建议保留尽可能多的代码与线程无关。保持困难且易出错的线程安全(如果我们非常幸运)代码至少。
答案 5 :(得分:1)
您的所有三个选项都可以使用。根据您的具体情况选择合适的一个将取决于您的情况。
如果您想要列表实现, CopyOnWriteArrayList
将会起作用,并且您不必介意每次编写时复制的基础存储。只要你没有很大的收藏品,这对表现来说非常有用。
ConcurrentHashMap
或&#34; ConcurrentHashSet
&#34; (如果您需要Collections.newSetFromMap
或Map
界面,则可以使用Set
},显然您不会以这种方式获得随机访问权限。一个伟大的!关于这两个问题的一点是,它们可以很好地处理大型数据集 - 当发生变异时,它们只会复制基础数据存储的一小部分。
答案 6 :(得分:0)
它取决于实现克隆/复制/ toArray(),new ArrayList(..)所需的结果,并且喜欢获取快照并且不锁定集合。 使用synchronized(集合)和迭代来确保在迭代结束时不会修改,即有效地锁定它。
旁注:(在内部需要创建临时ArrayList时,通常首选toArray(),但有一些例外。另请注意,除了toArray()之外的任何内容都应该包含在synchronized(集合)中,并使用Collections.synchronizedXXX提供。
答案 7 :(得分:0)
这个问题相当陈旧(对不起,我有点迟了......)但我还是想补充一下我的答案。
我会选择你的第二个选择(即在调用iterator()之前克隆该集合)但是有一个重大的转折。
假设,你想使用迭代器进行迭代,你不必在调用.iterator()之前使用Coppy,而是使用“松散地使用术语”否定“迭代”模式,但是你可以写一个“ThreadSafeIterator”。
它可以在同一个前提下工作,包括收集,但不要让迭代课知道,你就是这么做的。这样的迭代器可能看起来像这样:
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
类来确保线程安全等。但是用一粒盐去除方法。
关键是,通过使用这样的迭代器,没有人,迭代和迭代类(是真正的单词)都不必担心线程安全。