多线程环境下仅读HashMap是否需替换为ConcurrentHashMap?
问题描述
我创建了一个包含4种地址格式的Format枚举,已知各国家对应的地址格式,因此构建了以国家代码为键、地址格式为值的HashMap,期望以O(1)时间复杂度获取对应国家的地址格式。初始化代码如下:
static final HashMap<String, Format> countries = new HashMap<>(); static { for (Format format : Format.values()) { for (String country : format.getCountries()) { countries.put(country, format); } } } public static Optional<Format> fromCountry(String country) { return Optional.ofNullable(countries.get(country)); }
Format枚举示例如下:
FORMAT1("[num] [road]", new String[]{"AD", "AE", "AG", "......", "ZA", "ZM", "ZW"})
我的应用是多线程架构,但该Map完成初始化后不会再写入数据,仅通过fromCountry()方法执行读取操作。请问此场景下能否继续使用HashMap,还是必须改用ConcurrentHashMap?是否存在更优的实现方案?
回答
1. 可以继续使用HashMap
完全没问题,核心原因有两点:
- 你的
HashMap在静态代码块中初始化,JVM会保证类加载阶段的静态初始化逻辑是线程安全的——承载静态初始化的<clinit>方法只会被执行一次,不会出现多线程并发初始化导致Map数据不完整的情况。 - 初始化完成后只有读操作,
HashMap的get()方法在没有并发修改的前提下是线程安全的,不会出现读取脏数据或结构异常的问题。
哪怕是类延迟加载的场景,JVM也会保证类加载的原子性,不会出现Map初始化到一半就被线程读取的情况。
2. 不需要改用ConcurrentHashMap
ConcurrentHashMap是为并发读写场景设计的,它的get()方法虽然也是O(1),但会引入额外的同步开销(比如volatile变量读取、CAS操作)。而你的场景是初始化后只读,用它纯粹是性能浪费,完全没必要。
3. 更优实现方案
方案一:使用不可变Map(最推荐)
既然初始化后绝不修改,直接把Map转为不可变类型,既从根源上杜绝意外修改的可能,又能让代码语义更清晰,读性能和普通HashMap一致。
JDK原生实现:
static final Map<String, Format> countries; static { HashMap<String, Format> tempMap = new HashMap<>(); for (Format format : Format.values()) { for (String country : format.getCountries()) { tempMap.put(country, format); } } // 转为不可变Map,任何修改操作都会抛出UnsupportedOperationException countries = Collections.unmodifiableMap(tempMap); }
依赖Guava时的实现:
Guava的ImmutableMap是真正的不可变实现,内部结构不会被修改,读性能比JDK原生的不可变Map更优:
static final ImmutableMap<String, Format> countries; static { ImmutableMap.Builder<String, Format> builder = ImmutableMap.builder(); for (Format format : Format.values()) { for (String country : format.getCountries()) { builder.put(country, format); } } countries = builder.build(); }
方案二:将映射逻辑内聚到枚举中
如果不想单独维护Map,也可以把缓存逻辑直接写到Format枚举内部,让代码更内聚:
public enum Format { FORMAT1("[num] [road]", new String[]{"AD", "AE", "AG", "ZA", "ZM", "ZW"}), // 其他枚举值... private final String pattern; private final String[] countries; private static final Map<String, Format> COUNTRY_MAP; static { COUNTRY_MAP = new HashMap<>(); for (Format format : values()) { for (String country : format.countries) { COUNTRY_MAP.put(country, format); } } COUNTRY_MAP = Collections.unmodifiableMap(COUNTRY_MAP); } Format(String pattern, String[] countries) { this.pattern = pattern; this.countries = countries; } public static Optional<Format> fromCountry(String country) { return Optional.ofNullable(COUNTRY_MAP.get(country)); } // getter方法... }
内容的提问来源于stack exchange,提问作者Hacı Efendi
相关产品推荐
相关产品推荐

