集合上的线程安全迭代
我们都知道,当使用Collections.synchronizedXXX
(例如synchronizedSet()
)时,我们会获得底层集合的同步“视图”。
然而,这些包装器生成方法的文档指出,当使用迭代器迭代集合时,我们必须在集合上显式同步。
您选择哪个选项来解决这个问题?
我只能看到以下方法:
- 按照文档所述进行操作: 在集合上同步
- 在调用
iterator()
之前克隆集合 - 使用迭代器是线程安全的集合(我只知道 < code>CopyOnWriteArrayList/Set)
还有一个额外的问题:当使用同步视图时 - foreach/Iterable 的使用是线程安全的吗?
We all know when using Collections.synchronizedXXX
(e.g. synchronizedSet()
) we get a synchronized "view" of the underlying collection.
However, the document of these wrapper generation methods states that we have to explicitly synchronize on the collection when iterating of the collections using an iterator.
Which option do you choose to solve this problem?
I can only see the following approaches:
- Do it as the documentation states: synchronize on the collection
- Clone the collection before calling
iterator()
- Use a collection which iterator is thread-safe (I am only aware of
CopyOnWriteArrayList
/Set)
And as a bonus question: when using a synchronized view - is the use of foreach/Iterable thread-safe?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
您已经真正回答了您的额外问题:不,使用增强的 for 循环不安全 - 因为它使用迭代器。
至于哪种方法是最合适的 - 这实际上取决于您的上下文:
CopyOnWriteArrayList
可能是最合适的。You've already answered your bonus question really: no, using an enhanced for loop isn't safe - because it uses an iterator.
As for which is the most appropriate approach - it really depends on how your context:
CopyOnWriteArrayList
may be most appropriate.取决于您的访问模型。如果并发量低,写入频繁,1的性能最好。如果并发度高且写入不频繁,则 3 性能最佳。选项 2 几乎在所有情况下都表现不佳。
foreach
调用iterator()
,因此完全相同的事情也适用。Depends on your access model. If you have low concurrency and frequent writes, 1 will have the best performance. If you have high concurrency with and infrequent writes, 3 will have the best performance. Option 2 is going to perform badly in almost all cases.
foreach
callsiterator()
, so exactly the same things apply.您可以使用 Java 5.0 中添加的新集合之一,它支持迭代时的并发访问。另一种方法是使用线程安全的 toArray 进行复制(在复制期间)。
You could use one of the newer collections added in Java 5.0 which support concurrent access while iterating. Another approach is to take a copy using toArray which is thread safe (during the copy).
我可能完全不同意您的要求,但如果您不了解这些要求,请查看 google-考虑到“支持不变性”的集合。
I might be totally off with your requirements, but if you are not aware of them, check out google-collections with "Favor immutability" in mind.
我建议删除
Collections.synchronizedXXX
并在客户端代码中统一处理所有锁定。基本集合不支持线程代码中有用的复合操作,即使您使用 java.util.concurrent.* 代码也会更加困难。我建议尽可能多地保留与线程无关的代码。将困难且容易出错的线程安全(如果我们很幸运)代码保持在最低限度。I suggest dropping
Collections.synchronizedXXX
and handle all locking uniformly in the client code. The basic collections don't support the sort of compound operations useful in threaded code, and even if you usejava.util.concurrent.*
the code is more difficult. I suggest keeping as much code as possible thread-agnostic. Keep difficult and error-prone thread-safe (if we are very lucky) code to a minimum.您的所有三个选项都将起作用。选择适合您情况的产品取决于您的情况。
如果您想要一个列表实现并且不介意每次写入时都会复制底层存储,则
CopyOnWriteArrayList
将起作用。只要您没有很大的集合,这对于性能来说就非常好。如果您需要
Map
或,则
接口,显然这样你无法获得随机访问。太棒了!这两者的特点是它们可以很好地处理大型数据集——当它们发生变化时,它们只是复制底层数据存储的一小部分。ConcurrentHashMap
或“ConcurrentHashSet
”(使用Collections.newSetFromMap
)可以使用设置All three of your options will work. Choosing the right one for your situation will depend on what your situation is.
CopyOnWriteArrayList
will work if you want a list implementation and you don't mind the underlying storage being copied every time you write. This is pretty good for performance as long as you don't have very big collections.ConcurrentHashMap
or "ConcurrentHashSet
" (usingCollections.newSetFromMap
) will work if you need aMap
orSet
interface, obviously you don't get random access this way. One great! thing about these two is that they will work well with large data sets - when mutated they just copy little bits of the underlying data storage.它确实取决于实现克隆/复制/toArray()、new ArrayList(..) 等所需的结果,获得快照并且不锁定集合。
使用synchronized(collection)并通过确保迭代结束时的迭代将不会进行任何修改,即有效地锁定它。
旁注:(当内部需要创建临时 ArrayList 时,通常首选 toArray(),但有一些例外)。另请注意,除了 toArray() 之外的任何内容都应该包装在使用 Collections.synchronizedXXX 提供的同步(集合)中。
It does depend on the result one needs to achieve cloning/copying/toArray(), new ArrayList(..) and the likes obtain a snapshot and does not lock the the collection.
Using synchronized(collection) and iteration through ensure by the end of the iteration would be no modification, i.e. effectively locking it.
side note:(toArray() is usually preferred with some exceptions when internally it needs to create a temporary ArrayList). Also please note, anything but toArray() should be wrapped in synchronized(collection) as well, provided using Collections.synchronizedXXX.
这个问题相当旧(抱歉,我有点晚了..)但我仍然想添加我的答案。
我会选择你的第二个选择(即在调用 iterator() 之前克隆集合),但有一个重大的转变。
假设,您想使用迭代器进行迭代,则不必在调用 .iterator() 之前复制 Collection 并否定(我松散地使用术语“否定”)迭代器模式的想法,但您可以编写一个“线程安全迭代器”。
它会在相同的前提下工作,复制 Collection,但不让迭代类知道您就是这样做的。这样的迭代器可能如下所示:
更进一步,您可以使用信号量类来确保线程安全或其他东西。但对删除方法持保留态度。
关键是,通过使用这样的迭代器,任何人,无论是迭代还是被迭代的类(这是一个真正的词)都不必担心线程安全。
This Question is rather old (sorry, i am a bit late..) but i still want to add my Answer.
I would choose your second choice (i.e. Clone the collection before calling iterator()) but with a major twist.
Asuming, you want to iterate using iterator, you do not have to coppy the Collection before calling .iterator() and sort of negating (i am using the term "negating" loosely) the idea of the iterator pattern, but you could write a "ThreadSafeIterator".
It would work on the same premise, coppying the Collection, but without letting the iterating class know, that you did just that. Such an Iterator might look like this:
Taking this a Step furhter, you might use the
Semaphore
Class to ensure thread-safety or something. But take the remove method with a grain of salt.The point is, by using such an Iterator, no one, neither the iterating nor the iterated Class (is that a real word) has to worrie about Thread safety.