我试图以线程安全的方式懒惰地初始化地图。我在initialization-on-demand holder idiom之后提出了这个问题:
private static class NameIndexMapHolder {
private static final Map<String, Long> NAME_INDEX_MAP;
static {
Map<String, Long> map = new HashMap<>();
map.put("John", 3424534643);
map.put("Jane", 4328759749);
NAME_INDEX_MAP = Collections.unmodifiableMap(map);
}
}
public static Map<String, Long> getNameIndexMap() {
return NameIndexMapHolder.NAME_INDEX_MAP;
}
这有用吗?它是线程安全的吗?从我所读到的,这仅适用于单身人士。我读过的唯一另一种选择是双重检查锁定,这似乎有其自身的问题。
答案 0 :(得分:3)
保证以线程安全的方式初始化静态块。单身只是一个用例。您的代码完全是线程安全的。 See this discussion for more details.
然而,您的初始化只是伪懒惰(我自己的假设术语)。在代码中引用类(JVM的惰性类初始化)之前,不会初始化类,但是您的映射在技术上会立即初始化(首次访问类时)。 See this discussion for more information on class loading.
答案 1 :(得分:1)
是的,这是线程安全且懒惰的。
首先,让我们看看它是否是线程安全的:
NAME_INDEX_MAP
的引用吗? 否。这种情况发生在静态初始化程序中,仅在初始化类时执行(JLS 8.7)。类初始化使用同步来确保只有一个线程将执行初始化程序(JLS 12.4.2)。1:在初始化锁定LC上同步C. ...
...
4:如果C的Class对象表明C已经初始化,则不需要进一步的操作。释放LC并正常完成。
...
Java虚拟机实现可以通过在步骤1中删除锁获取(并在步骤4/5中释放)来优化此过程,此时它可以确定类的初始化已经完成,前提是,就Java内存模型,所有发生在排序之前(JLS§17.4.5),如果获得锁定,将存在,在执行优化时仍然存在。
(JVMS 5.5,重点补充)
那么,它是懒惰的吗?是;类初始化仅在第一次访问该类的任何成员(JLS 12.4.1)之前立即发生,在这种情况下,您只有一个字段。因此初始化只会在您第一次访问NAME_INDEX_MAP
之前发生,这是您想要的懒惰。
答案 2 :(得分:0)
除非我要为java编写代码&lt; 5我会选择双重检查锁定:
public final class Example {
private static final Object LOCK = new Object();
private static volatile Map<String, Long> NAME_INDEX_MAP;
public static Map<String, Long> getNameIndexMap() {
if (null == NAME_INDEX_MAP) {
synchronized (LOCK) {
if (null == NAME_INDEX_MAP) {
NAME_INDEX_MAP = new HashMap<>();
NAME_INDEX_MAP.put("abc", 123);
//maybe make it immutable or use a ConcurrentMap instead?
NAME_INDEX_MAP = Collections.unmodifiableMap(NAME_INDEX_MAP);
}
}
}
return NAME_INDEX_MAP;
}
}
使用双重检查的锁定习惯用于java&gt; = 5的关键是声明地图引用volatile
,这将使JVM放置必要的先发生约束。