如果我在通过Collections.unmodifiableSet()运行它后有一个HashSet实例,它是否是线程安全的?
我问这个,因为Set文档声明它不是,但我只是执行读操作。
答案 0 :(得分:40)
来自Javadoc:
请注意,此实现未同步。如果多个线程同时访问哈希集,并且至少有一个线程修改了该集,那么它必须在外部进行同步
阅读不会修改集合,因此你没事。
答案 1 :(得分:13)
HashSet
将是线程安全的。这并不意味着您传递给Collections.unmodifiableSet()
的任何集将是线程安全的。
想象一下这个contains
的天真实现缓存了最后检查的值:
Object lastKey;
boolean lastContains;
public boolean contains(Object key) {
if ( key == lastKey ) {
return lastContains;
} else {
lastKey = key;
lastContains = doContains(key);
return lastContains;
}
}
显然,这不是线程安全的。
答案 2 :(得分:11)
这将是线程安全的,但仅仅是因为Collections.unmodifiableSet()
在内部以安全的方式(通过Set
字段)发布目标final
。
请注意,一般来说,例如“只读对象始终是线程安全的”并不正确,因为它们没有考虑操作重新排序的可能性。
(理论上)可能的是,由于操作重新排序,在完全初始化对象并填充数据之前,对该只读对象的引用将对其他线程可见。要消除这种可能性,您需要以安全的方式发布对象的引用,例如,将它们存储在final
字段中,就像Collections.unmodifiableSet()
一样。
答案 3 :(得分:6)
如果你不改变它,每个数据结构都是线程安全的。
因为你必须改变HashSet才能初始化它,所以必须在初始化集合的线程和所有读取线程之间进行一次同步。你必须只做一个时间。例如,当您将对不可修改集的引用传递给之前从未触及它的新线程时。
答案 4 :(得分:2)
是的,并发读访问是安全的。以下是文档中的相关句子:
如果多个线程同时访问哈希集,并且至少有一个线程修改了该集,则必须在外部进行同步。
它声明如果at least one
线程修改它,您只需要同步。
答案 5 :(得分:2)
我不相信它是线程安全的只是因为你运行Collections.unmodifiableSet()。即使HashSet完全初始化并且您将其标记为不可修改,也不意味着其他线程可以看到这些更改。更糟糕的是,在没有同步的情况下,允许编译器重新排序指令,这可能意味着读取线程不仅会看到丢失的数据,而且还会看到处于奇怪状态的散列集。因此,您需要一些同步。我相信解决这个问题的一种方法是将hashset创建为final并在构造函数中完全初始化它。这是一篇关于JMM http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html的好文章。阅读有关新JMM下最终字段如何工作的部分?
能够看到字段的正确构造值是很好的,但如果字段本身是引用,那么您还希望代码查看它指向的对象(或数组)的最新值。如果您的字段是最终字段,则也可以保证。因此,您可以拥有一个指向数组的最终指针,而不必担心其他线程看到数组引用的正确值,但是数组内容的值不正确。同样,在这里“正确”,我们的意思是“对象的构造函数结束时的最新”,而不是“最新的可用值”。
答案 6 :(得分:-2)
如果永远不会更改共享内存,则无需同步即可始终读取。使集合不可修改只会强制执行无法写入的事实。