Java HashSet是否为只读的线程安全?

如果我在通过Collections.unmodifiableSet()运行它后有一个HashSet实例,它是否是线程安全的?

我问这个,因为Set文档声明它不是,但我只是执行读操作。

来自Javadoc:

请注意,此实现不同步。 如果多个线程同时访问哈希集,并且至少有一个线程修改了该集,则必须在外部进行同步

阅读不会修改一套,所以你没事。

如果以只读方式使用, HashSet将是线程安全的。 这并不意味着您传递给Collections.unmodifiableSet() 任何 Set都将是线程安全的。

想象一下这个contains天真实现缓存最后检查的值:

 Object lastKey; boolean lastContains; public boolean contains(Object key) { if ( key == lastKey ) { return lastContains; } else { lastKey = key; lastContains = doContains(key); return lastContains; } } 

显然,这不是线程安全的。

它将是线程安全的,但仅仅是因为Collections.unmodifiableSet()内部以安全的方式(通过final字段)发布目标Set

请注意,通常诸如“只读对象始终是线程安全的”之类的语句是不正确的,因为它们没有考虑操作重新排序的可能性。

(理论上)可能的是,由于操作重新排序,在完全初始化对象并填充数据之前,对该只读对象的引用将对其他线程可见。 要消除这种可能性,您需要以安全的方式发布对象的引用,例如,通过将它们存储在final字段中,就像Collections.unmodifiableSet()

如果不改变它,每个数据结构都是线程安全的。

因为你必须改变HashSet才能初始化它,所以必须在初始化集合的线程和所有读取线程之间进行一次同步。 你必须只做一次。 例如,当您将对不可修改集的引用传递给之前从未触及它的新线程时。

我不相信它是线程安全的只是因为你运行Collections.unmodifiableSet()。 即使HashSet完全初始化并且您将其标记为不可修改,也不意味着其他线程可以看到这些更改。 更糟糕的是,在没有同步的情况下,允许编译器重新排序指令,这可能意味着读取线程不仅会看到丢失的数据,而且还会看到处于奇怪状态的hashset。 因此,您需要一些同步。 我相信这方面的一种方法是将hashset创建为final并在构造函数中完全初始化它。 这是一篇关于JMM http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html的好文章。 阅读有关新JMM下最终字段如何工作的部分?

能够查看字段的正确构造值是很好的,但如果字段本身是引用,那么您还希望代码查看它指向的对象(或数组)的最新值。 如果您的字段是最终字段,则也可以保证。 因此,您可以拥有一个指向数组的最终指针,而不必担心其他线程看到数组引用的正确值,但是数组内容的值不正确。 同样,在这里“正确”,我们的意思是“对象的构造函数结束时的最新”,而不是“可用的最新值”。

是的,它对于并发读访问是安全的。 以下是文档中的相关句子:

如果多个线程同时访问哈希集,并且至少有一个线程修改了该集,则必须在外部进行同步。

它声明如果at least one线程修改它,您只需要同步。

如果永远不会更改共享内存,则无需同步即可始终读取。 使集合不可修改只会强制执行无法写入的事实。