19

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

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

你选择哪个选项来解决这个问题?

我只能看到以下方法:

  1. 按照文档所述进行操作:在集合上同步
  2. 在调用之前克隆集合iterator()
  3. 使用迭代器是线程安全的集合(我只知道CopyOnWriteArrayList/Set)

作为一个额外的问题:使用同步视图时 - 使用 foreach/Iterable 线程安全吗?

4

8 回答 8

27

您已经真正回答了您的奖励问题:不,使用增强的 for 循环不安全的 - 因为它使用迭代器。

至于哪种方法最合适 - 这实际上取决于您的上下文:

  • 写的很少吗?如果是这样,CopyOnWriteArrayList可能是最合适的。
  • 集合是否相当小,并且迭代很快?(即您在循环中没有做太多工作)如果是这样,同步可能会很好- 特别是如果这种情况不经常发生(即您不会对集合有太多争用)。
  • 如果您正在做大量工作并且不想阻止其他线程同时工作,那么克隆集合的成功可能是可以接受的。
于 2010-12-23T10:15:27.080 回答
6

取决于您的访问模式。如果并发低,写频繁,1 性能最好。如果您有高并发和不频繁写入,则 3 将具有最佳性能。选项 2 在几乎所有情况下都会表现不佳。

foreach调用iterator(),所以完全相同的事情适用。

于 2010-12-23T10:18:05.200 回答
4

您可以使用 Java 5.0 中添加的新集合之一,它支持迭代时的并发访问。另一种方法是使用线程安全的 toArray 进行复制(在复制期间)。

Collection<String> words = ...
// enhanced for loop over an array.
for(String word: words.toArray(new String[0])) {

}
于 2010-12-23T13:39:11.910 回答
1

我可能完全不同意您的要求,但如果您不了解这些要求,请查看带有“Favor immutability”的google-collections 。

于 2010-12-23T10:48:29.423 回答
1

Collections.synchronizedXXX我建议在客户端代码中统一删除和处理所有锁定。基本集合不支持在线程代码中有用的那种复合操作,即使您使用java.util.concurrent.*代码也比较困难。我建议尽可能多地保留与线程无关的代码。将困难且容易出错的线程安全(如果我们很幸运的话)代码保持在最低限度。

于 2010-12-23T13:06:59.033 回答
1

您的所有三个选项都将起作用。为您的情况选择合适的将取决于您的情况。

CopyOnWriteArrayList如果您想要一个列表实现并且您不介意每次编写时都复制底层存储,那么它将起作用。只要您没有非常大的集合,这对于性能来说非常好。

ConcurrentHashMap如果您需要or接口,或 " ConcurrentHashSet" (使用)将起作用,显然您不会以这种方式获得随机访问。一个伟大的!关于这两者的事情是它们可以很好地处理大型数据集 - 当发生突变时,它们只会复制底层数据存储的一小部分。Collections.newSetFromMapMapSet

于 2015-04-01T10:29:27.150 回答
0

它确实取决于实现克隆/复制/toArray()、new ArrayList(..) 等获取快照并且不锁定集合所需的结果。在迭代结束时使用 synchronized(collection) 和通过确保迭代将不会被修改,即有效地锁定它。

旁注:(当内部需要创建一个临时 ArrayList 时,通常首选 toArray(),但有一些例外)。另请注意,除了 toArray() 之外的任何内容也应包含在 synchronized(collection) 中,使用 Collections.synchronizedXXX 提供。

于 2011-01-03T22:26:25.893 回答
0

这个问题相当老了(对不起,我有点晚了..)但我仍然想添加我的答案。

我会选择您的第二个选择(即在调用 iterator() 之前克隆集合),但有一个重大转折。

假设,你想使用迭代器进行迭代,你不必在调用 .iterator() 之前复制 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);
        }
    }
}

更进一步,您可能会使用SemaphoreClass 来确保线程安全或其他东西。但是采取带有一粒盐的去除方法。

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

于 2018-02-23T16:19:59.073 回答