繁体   English   中英

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

[英]Thread safe Lazy loading of a static map

我正在尝试以线程安全的方式延迟初始化地图。 我想出了以下这个按需初始化持有人的习惯用法

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;
}

这样行吗? 它是线程安全的吗? 根据我的阅读,这仅适用于Singletons。 我读过的唯一其他选择是双重检查锁定,它似乎有其自身的问题。

保证静态块以线程安全的方式初始化。 单例只是一个用例。 您的代码是完全线程安全的。 有关更多详细信息,请参见此讨论。

但是,您的初始化只是伪惰性的 (我自己的虚构术语)。 直到在代码中引用类后,类才会初始化(JVM进行的懒类初始化),但是从技术上讲,您的映射会立即进行初始化(首次访问类时)。 有关类加载的更多信息,请参见此讨论。

是的,这是线程安全和惰性的。

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

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

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

    ...

    4:如果C的Class对象指示C已被初始化,则无需采取进一步的措施。 释放LC并正常完成。

    ...

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

    JVMS 5.5 ,添加了重点)

  3. 发布后会修改对象吗? 不会它包装在unmodifiableMap中,没有其他对基础的可修改HashMap的引用。

那么,这是懒惰的吗? 是; 类初始化仅在第一次访问该类的任何成员之前立即发生( JLS 12.4.1 ),在这种情况下,您只有一个字段。 因此,初始化只会在您第一次访问NAME_INDEX_MAP之前立即进行,这是您想要的惰性。

除非我要编写java <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> = 5的关键是声明映射引用为volatile ,这将使JVM在约束之前进行必要的操作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM