繁体   English   中英

在Java中获取值时如何实现线程安全的HashMap延迟初始化?

[英]How to implement thread-safe HashMap lazy initialization when getting value in Java?

我想实现一个通过字符串值获取 Enum 对象的实用程序。 这是我的实现。

字符串枚举

public interface IStringEnum {
    String getValue();
}

字符串枚举实用程序

public class StringEnumUtil {
    private volatile static Map<String, Map<String, Enum>> stringEnumMap = new HashMap<>();

    private StringEnumUtil() {}

    public static <T extends Enum<T>> Enum fromString(Class<T> enumClass, String symbol) {
        final String enumClassName = enumClass.getName();
        if (!stringEnumMap.containsKey(enumClassName)) {
            synchronized (enumClass) {
                if (!stringEnumMap.containsKey(enumClassName)) {
                    System.out.println("aaa:" + stringEnumMap.get(enumClassName));
                    Map<String, Enum> innerMap = new HashMap<>();
                    EnumSet<T> set = EnumSet.allOf(enumClass);
                    for (Enum e: set) {
                        if (e instanceof IStringEnum) {
                            innerMap.put(((IStringEnum) e).getValue(), e);
                        }
                    }
                    stringEnumMap.put(enumClassName, innerMap);
                }
            }
        }
        return stringEnumMap.get(enumClassName).get(symbol);
    }
}

我写了一个单元测试来测试它是否在多线程情况下工作。

StringEnumUtilTest.java

public class StringEnumUtilTest {
    enum TestEnum implements IStringEnum {
        ONE("one");
        TestEnum(String value) {
            this.value = value;
        }
        @Override
        public String getValue() {
            return this.value;
        }
        private String value;
    }

    @Test
    public void testFromStringMultiThreadShouldOk() {
        final int numThread = 100;
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch doneLatch = new CountDownLatch(numThread);
        List<Boolean> resultList = new LinkedList<>();
        for (int i = 0; i < numThread; ++i) {
            new Thread(() -> {
                try {
                    startLatch.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                resultList.add(StringEnumUtil.fromString(TestEnum.class, "one") != null);
                doneLatch.countDown();
            }).start();
        }
        startLatch.countDown();
        try {
            doneLatch.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
        assertEquals(numThread, resultList.stream().filter(item -> item.booleanValue()).count());
    }
}

测试结果为:

aaa:null

java.lang.AssertionError: 
Expected :100
Actual   :98

它表示只有一个线程执行这行代码:

System.out.println("aaa:" + stringEnumMap.get(enumClassName));

所以初始化代码应该只由一个线程执行。

奇怪的是,执行这行代码后,某个线程的结果会为null

return stringEnumMap.get(enumClassName).get(symbol);

由于没有 NullPointerException, stringEnumMap.get(enumClassName)必须返回innerMap的引用。 但是为什么在调用innerMap get(symbol)后会得到null呢?

请帮忙,它让我整天发疯!

stringEnumMap应该是ConcurrentHashMap<String, Map<String,Enum>> ,并使用computeIfAbsent进行延迟初始化。

问题是由于线路

List<Boolean> resultList = new LinkedList<>();

来自LinkedList 的 JavaDoc

请注意,此实现不是同步的。 如果多个线程并发访问一个链表,并且至少有一个线程在结构上修改了链表,则必须进行外部同步。 (结构修改是添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这通常是通过同步一些自然封装列表的对象来完成的。如果不存在这样的对象,应该使用 Collections.synchronizedList 方法“包装”列表。 这最好在创建时完成,以防止对列表的意外不同步访问:
List list = Collections.synchronizedList(new LinkedList(...));

由于LinkedList不是线程安全的,因此在add操作期间可能会发生意外行为。 这导致resultList大小小于线程计数,因此预期计数小于结果计数。
要获得正确的结果,请按照建议添加Collections.synchronizedList

尽管您的实施很好,但我建议您遵循 Matt Timmermans 的答案以获得更简单和强大的解决方案。

ConcurrentMap接口

正如其他人指出的那样,如果跨线程操作Map ,则必须考虑并发性。

您可以自己处理并发访问。 但是没有必要。 Java 带有两个Map实现,它们是为内部处理并发而构建的。 这些实现实现了ConcurrentMap接口。

  • ConcurrentSkipListMap
  • ConcurrentHashMap

第一个按排序顺序维护键,实现NavigableMap接口。

这是我编写的一个表格,用于显示与 Java 11 捆绑在一起的Map的所有实现的特征。

Java 11 中的地图实现表,比较它们的特性

您可能会发现ConcurrentMap接口的其他第三方实现。

尝试移动
if (!stringEnumMap.containsKey(enumClassName))

return stringEnumMap.get(enumClassName).get(symbol);
进入同步块。

暂无
暂无

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

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