我在初始化时完全加载了一个java HashMap,但在初始化之后,多个线程将从HashMap中读取数据。我想避免任何类型的同步,因为地图基本上是只读的,永远不会改变。但我可以保证所有线程都可以看到所有键和值吗?
答案 0 :(得分:8)
如果地图的内容永远不会改变,那么您就没有问题。只有当变量的内容发生变化时,内存模型可见性问题才会发挥作用。
您可能希望同步地图的初始化,以确保没有线程在完全初始化之前访问它,并确保加载到地图中的值都是可见的。
编辑:最初我完全忽略了地图如何初始化的问题。在阅读one of the Pugh articles之后(再次),似乎地图确实需要是最终的,以便初始化数据变得可见:
能够看到字段正确构造的值很好,但如果字段本身是引用,那么您还希望代码查看它指向的对象(或数组)的最新值。如果您的字段是最终字段,则也可以保证。因此,您可以拥有一个指向数组的最终指针,而不必担心其他线程看到数组引用的正确值,但是数组内容的值不正确。同样,在这里“正确”,我们的意思是“对象的构造函数结束时的最新”,而不是“最新的可用值”。
有一个强制“发生在之前”关系的条件列表,在Java规范中给出,我应该在这里引用它们(或者如果其他人在他们的答案中做了,我会投票给它)。静态变量和Holder成语当然是一种方法。问题非常广泛,因为它没有指定地图如何初始化,如果你发布一个描述你如何建议进行初始化的问题,你可能会得到一个更直接有用的答案。
答案 1 :(得分:4)
如果您将HashMap声明为final,并且您预先初始化了一个本地HashMap,那么在构造函数初始化之后将全局HashMap与本地存储在一起,以便HashMap的内容可以被视为可见。
必须正确使用最终字段 提供不变性的保证。 一个对象被认为是 它完全初始化了 构造函数完成。一个线程 只能看到对象的引用 在那个对象完全之后 初始化保证看到 正确初始化的值 对象的最终字段。
答案 2 :(得分:2)
使用Guava的ImmutableMap将是最佳解决方案。
答案 3 :(得分:2)
如果所有线程都只是读取它,则不需要同步地图。为了确保不变性,我会在初始化后将Map转换为不可修改的Map:
map = Collections.unmodifiableMap(map);
如果某个线程调用了一个修改Map的操作,则会抛出UnsupportedOperationException
。
答案 4 :(得分:1)
我想安全的方法是将其声明为final并在构造函数中初始化它:http://www.javamex.com/tutorials/synchronization_final.shtml
答案 5 :(得分:0)
只要初始化在读取开始之前完成,就没有理由为什么HashMap的所有内容都不会对每个线程都可见。
答案 6 :(得分:0)
这是正确答案。
我打赌你的地图是一个静态字段,然后是的,没有同步就可以安全地读取它。
class SomeClass
static Map map = init();
这是因为JVM对类初始化执行了隐式的双重检查锁定。
基本上,你想要一个单身人士。有几种技术,使用静态字段就是其中之一。我敢打赌你的地图是一个“全局”的东西,所以自然它是一个静态字段,因此线程安全。
有些情况下,懒惰的初始化数据结构不是全局的,因此我们需要其他单例实现方案,包括急切同步,易失性引用或通过中间最终引用。请参阅维基百科双重检查锁定