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
线程修改它,您只需要同步。
如果永远不会更改共享内存,则无需同步即可始终读取。 使集合不可修改只会强制执行无法写入的事实。