线程安全延迟加载静态映射

时间:2016-10-15 13:35:23

标签: java multithreading design-patterns

我试图以线程安全的方式懒惰地初始化地图。我在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;
}

这有用吗?它是线程安全的吗?从我所读到的,这仅适用于单身人士。我读过的唯一另一种选择是双重检查锁定,这似乎有其自身的问题。

3 个答案:

答案 0 :(得分:3)

保证以线程安全的方式初始化静态块。单身只是一个用例。您的代码完全是线程安全的。 See this discussion for more details.

然而,您的初始化只是伪懒惰(我自己的假设术语)。在代码中引用类(JVM的惰性类初始化)之前,不会初始化类,但是您的映射在技术上会立即初始化(首次访问类时)。 See this discussion for more information on class loading.

答案 1 :(得分:1)

是的,这是线程安全且懒惰的。

首先,让我们看看它是否是线程安全的:

  1. 两个线程可以分配对NAME_INDEX_MAP的引用吗? 否。这种情况发生在静态初始化程序中,仅在初始化类时执行(JLS 8.7)。类初始化使用同步来确保只有一个线程将执行初始化程序(JLS 12.4.2)。
  2. 初始出版物是否安全? 是。 JLS实际上有点模糊,但VM定义(JVMS)是明确的:
      

    1:在初始化锁定LC上同步C. ...

         

    ...

         

    4:如果C的Class对象表明C已经初始化,则不需要进一步的操作。释放LC并正常完成。

         

    ...

         

    Java虚拟机实现可以通过在步骤1中删除锁获取(并在步骤4/5中释放)来优化此过程,此时它可以确定类的初始化已经完成,前提是,就Java内存模型,所有发生在排序之前(JLS§17.4.5),如果获得锁定,将存在,在执行优化时仍然存在

         

    JVMS 5.5,重点补充)

  3. 发布后是否会修改对象? 不。它包含在一个不可修改的Map中,没有其他对底层可修改的HashMap的引用。
  4. 那么,它是懒惰的吗?是;类初始化仅在第一次访问该类的任何成员(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放置必要的先发生约束。