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