对集合进行线程安全迭代

时间:2010-12-23 10:00:36

标签: java multithreading collections iterator thread-safety

我们都知道在使用Collections.synchronizedXXX时(例如synchronizedSet()),我们会获得基础集合的同步“视图”。

但是,这些包装器生成方法的文档指出,在使用迭代器迭代集合时,我们必须在集合上显式同步

您选择哪个选项来解决此问题?

我只能看到以下方法:

  1. 按照文档说明:对集合进行同步
  2. 在致电iterator()
  3. 之前克隆收藏品
  4. 使用迭代器是线程安全的集合(我只知道CopyOnWriteArrayList / Set)
  5. 作为一个额外的问题:当使用同步视图时 - 是使用foreach / Iterable线程安全吗?

8 个答案:

答案 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.newSetFromMapMap界面,则可以使用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类来确保线程安全等。但是用一粒盐去除方法。

关键是,通过使用这样的迭代器,没有人,迭代和迭代类(是真正的单词)都不必担心线程安全。