4 回答
TA贡献1875条经验 获得超5个赞
当且仅当对的引用HashMap被安全发布时,您的惯用法才是安全的。而不是有关的内部事情HashMap本身,安全出版物与构建线程如何使得参考地图到其他线程是可见的交易。
基本上,这里唯一可能的竞争是在构造HashMap与可能在其完全构造之前可以访问它的任何读取线程之间进行竞争。大部分讨论是关于map对象状态发生的事情,但这是无关紧要的,因为您从未修改过它-因此,唯一有趣的部分是如何HashMap发布参考。
例如,假设您像这样发布地图:
class SomeClass {
public static HashMap<Object, Object> MAP;
public synchronized static setMap(HashMap<Object, Object> m) {
MAP = m;
}
}
...,并在某个时刻setMap()通过地图调用,其他线程正在使用SomeClass.MAP该地图,并检查是否为null,如下所示:
HashMap<Object,Object> map = SomeClass.MAP;
if (map != null) {
.. use the map
} else {
.. some default behavior
}
即使看起来确实如此,这也不安全。问题在于,的集合与另一个线程上的后续读取之间没有任何事前发生的关系SomeObject.MAP,因此读取线程可以自由查看部分构造的映射。这几乎可以做任何事情,甚至在实践中它也可以做一些事情,例如将读取线程置于无限循环中。
为了安全地发布地图,您需要建立之前发生的关系的参考书面的HashMap(即出版)和引用(即消费)的后续读者。方便地,只有几种易于记忆的方法可以实现这一目标[1]:
通过适当锁定的字段交换参考(JLS 17.4.5)
使用静态初始化程序进行初始化存储(JLS 12.4)
通过volatile字段(JLS 17.4.5)或由于此规则而通过AtomicX类交换引用
将值初始化为最终字段(JLS 17.5)。
对于您的情况最有趣的是(2),(3)和(4)。特别是(3)直接适用于我上面的代码:如果将以下声明转换MAP为:
public static volatile HashMap<Object, Object> MAP;
那么一切都是洁净的:看到非null值的读者必然与该存储与to 发生事前关系,MAP从而看到与地图初始化关联的所有存储。
其他方法会更改方法的语义,因为(2)(使用静态初始化器)和(4)(使用final)都暗示您不能MAP在运行时动态设置。如果您不需要这样做,则只需声明MAP为a static final HashMap<>即可确保发布安全。
实际上,这些规则对于安全访问“未经修改的对象”很简单:
如果要发布的对象不是固有不变的(如在声明的所有字段中一样final),并且:
您已经可以创建将在声明a时分配的对象:仅使用一个final字段(包括static final静态成员)。
您希望稍后在引用可见之后分配对象:使用volatile字段b。
而已!
实际上,它非常有效。static final例如,使用字段可以使JVM假定该值在程序生命周期内保持不变,并且可以对其进行大量优化。final成员字段的使用允许大多数体系结构以与普通字段读取等效的方式读取该字段,并且不会阻止进一步的优化c。
最后,使用volatile确实会产生一些影响:在许多体系结构(例如x86,尤其是那些不允许读取传递读取的结构)上都不需要硬件障碍,但是在编译时可能不会发生一些优化和重新排序-但这效果一般很小。作为交换,您实际上得到的不仅仅是所需的内容-您不仅可以安全地发布一个HashMap,而且可以存储更多未修改的HashMaps,以使用相同的参考文献,并确保所有读者都能看到安全发布的地图。
有关更多详细信息,请参阅Shipilev或Manson和Goetz的本常见问题解答。
[1]直接引用shipilev。
一个这听起来很复杂,但我的意思是,你可以在指定施工时间参考-无论是在申报点或在构造函数(成员字段)或静态初始化(静态字段)。
b(可选)您可以使用一种synchronized方法来获取/设置或一个AtomicReference或某物,但是我们正在谈论的是您可以做的最少工作。
c。与非常弱的内存模型(我在看一些架构你,阿尔法)可能需要之前某些类型的读屏障的final读-但是今天这些都是非常罕见的。
TA贡献1862条经验 获得超7个赞
从同步的角度来看,读取是安全的,但从内存的角度来看,则不是。在Java开发人员中,包括在Stackoverflow上,这都是人们普遍误解的东西。(观察该答案的等级以作证明。)
如果您正在运行其他线程,如果当前线程没有写出内存,则他们可能看不到HashMap的更新副本。通过使用synced或volatile关键字,或通过使用某些Java并发构造,可以进行内存写操作。
添加回答
举报