如果我在Collections.unmodifiableSet()中运行了一个HashSet示例,它是否是线程安全的?我之所以这样问,是因为Set文档说明它不是,但我只执行读操作。
ijxebb2r1#
在Javadoc中:请注意,此实现是不同步的。如果多个线程并发访问一个哈希集,并且至少有一个线程修改了该集,则必须在外部进行同步阅读不会改变一个集合,所以你很好。
ivqmmu1c2#
如果HashSet以只读方式使用,它将是线程安全的。这并不意味着您传递给Collections.unmodifiableSet()的 any Set将是线程安全的。想象一下contains的简单实现,它缓存了最后检查的值:
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; } }
显然,这不是线程安全的。
o7jaxewo3#
它将是线程安全的,但这仅仅是由于Collections.unmodifiableSet()以安全的方式(通过final字段)在内部发布目标Set的事实。
final
Set
请注意,一般而言,诸如“只读对象始终是线程安全的”之类的语句是不正确的,因为它们没有考虑操作重新排序的可能性。
理论上讲,由于操作重新排序,在对象完全初始化并填充数据之前,对该只读对象的引用可能会对其他线程可见。为了消除这种可能性,您需要以安全的方式发布对该对象的引用,例如,将它们存储在final字段中,就像Collections.unmodifiableSet()所做的那样。
tvz2xvvm4#
每个数据结构都是线程安全的,如果你不改变它。因为你必须改变一个HashSet才能初始化它,所以有必要在初始化这个集合的线程和所有读取它的线程之间同步一次。你只需要做 * 一次 *。例如,当你把对不可修改集合的引用传递给一个新线程,而这个新线程以前从未接触过它。
tf7tbtn25#
我不相信仅仅因为运行Collections就能保证线程安全。().即使HashSet已经完全初始化,并且您将其标记为不可修改,也不意味着这些更改对其他线程可见.更糟糕的是,在没有同步的情况下,编译器可以重新排序指令,这意味着阅读线程不仅可以看到丢失的数据,还可以看到处于wierd状态的哈希集,因此需要进行一些同步。我认为解决这个问题的一种方法是将散列集创建为final,并在构造函数中完全初始化它。
能够看到字段的正确构造值是很好的,但是如果字段本身是一个引用,那么您还希望代码能够看到对象的最新值(或数组)。如果您的字段是final字段,这也是可以保证的。因此,你可以有一个指向数组的最终指针,而不必担心其他线程看到的数组引用的值是正确的,但数组内容的值是不正确的。2同样,这里的“正确”是指“对象构造函数结束时的最新值”,而不是“可用的最新值”。
dzjeubhm6#
是的,它对于并发读访问是安全的。下面是文档中的相关语句:如果多个执行绪同时存取杂凑集,而且至少有一个执行绪修改了该集,则必须在外部同步行程该集。它声明只有在at least one线程修改它时才需要同步。来源:https://docs.oracle.com/javase/8/docs/api/java/util/HashSet.html
at least one
yk9xbfzb7#
如果共享内存永远不会被更改,那么你可以在不进行同步的情况下进行读取。使该集合不可修改只会强制执行不能进行写入的事实。
7条答案
按热度按时间ijxebb2r1#
在Javadoc中:
请注意,此实现是不同步的。如果多个线程并发访问一个哈希集,并且至少有一个线程修改了该集,则必须在外部进行同步
阅读不会改变一个集合,所以你很好。
ivqmmu1c2#
如果
HashSet
以只读方式使用,它将是线程安全的。这并不意味着您传递给Collections.unmodifiableSet()
的 any Set将是线程安全的。想象一下
contains
的简单实现,它缓存了最后检查的值:显然,这不是线程安全的。
o7jaxewo3#
它将是线程安全的,但这仅仅是由于
Collections.unmodifiableSet()
以安全的方式(通过final
字段)在内部发布目标Set
的事实。请注意,一般而言,诸如“只读对象始终是线程安全的”之类的语句是不正确的,因为它们没有考虑操作重新排序的可能性。
理论上讲,由于操作重新排序,在对象完全初始化并填充数据之前,对该只读对象的引用可能会对其他线程可见。为了消除这种可能性,您需要以安全的方式发布对该对象的引用,例如,将它们存储在
final
字段中,就像Collections.unmodifiableSet()
所做的那样。tvz2xvvm4#
每个数据结构都是线程安全的,如果你不改变它。
因为你必须改变一个HashSet才能初始化它,所以有必要在初始化这个集合的线程和所有读取它的线程之间同步一次。你只需要做 * 一次 *。例如,当你把对不可修改集合的引用传递给一个新线程,而这个新线程以前从未接触过它。
tf7tbtn25#
我不相信仅仅因为运行Collections就能保证线程安全。().即使HashSet已经完全初始化,并且您将其标记为不可修改,也不意味着这些更改对其他线程可见.更糟糕的是,在没有同步的情况下,编译器可以重新排序指令,这意味着阅读线程不仅可以看到丢失的数据,还可以看到处于wierd状态的哈希集,因此需要进行一些同步。我认为解决这个问题的一种方法是将散列集创建为final,并在构造函数中完全初始化它。
能够看到字段的正确构造值是很好的,但是如果字段本身是一个引用,那么您还希望代码能够看到对象的最新值(或数组)。如果您的字段是final字段,这也是可以保证的。因此,你可以有一个指向数组的最终指针,而不必担心其他线程看到的数组引用的值是正确的,但数组内容的值是不正确的。2同样,这里的“正确”是指“对象构造函数结束时的最新值”,而不是“可用的最新值”。
dzjeubhm6#
是的,它对于并发读访问是安全的。下面是文档中的相关语句:
如果多个执行绪同时存取杂凑集,而且至少有一个执行绪修改了该集,则必须在外部同步行程该集。
它声明只有在
at least one
线程修改它时才需要同步。来源:https://docs.oracle.com/javase/8/docs/api/java/util/HashSet.html
yk9xbfzb7#
如果共享内存永远不会被更改,那么你可以在不进行同步的情况下进行读取。使该集合不可修改只会强制执行不能进行写入的事实。