是Java HashSet线程安全的只读?

时间:2011-03-21 15:25:21

标签: java concurrency hashset

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

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

7 个答案:

答案 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)

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