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

标签 java multithreading java.util.concurrent concurrenthashmap

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

IStringEnum.java

public interface IStringEnum {
    String getValue();
}

StringEnumUtil.java

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 的引用。但为什么调用innerMapget(symbol)后会得到null呢?

请帮忙,这让我发疯一整天!

最佳答案

问题出在线路上

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

来自JavaDoc of LinkedList :

Note that this implementation is not synchronized.If multiple threads access a linked list concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list.If no such object exists, the list should be "wrapped" using the Collections.synchronizedListmethod. This is best done at creation time, to prevent accidental unsynchronized access to the list:
List list = Collections.synchronizedList(new LinkedList(...));

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

虽然你的实现很好,但我建议你遵循 Matt Timmermans 的回答以获得更简单和强大的解决方案。

关于java - Java中如何实现线程安全的HashMap取值时延迟初始化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60465850/

相关文章:

java - 字符串请求发布未显示任何错误

java - 如何在我的线程中断后立即中断 RestTemplate 调用?

c - 即使在c中使用线程,如何按顺序打印结果?

multithreading - 尝试使用Perl6.org上演示的线程

Java 并发 - 中断策略

multithreading - CountDownLatch 中 await() 的目的是什么?

java - CopyOnWriteArraySet 中不允许重复,即使它使用内部 CopyOnWriteArrayList 进行所有操作

java - 使用身份 id 在 derby 中导入数据

java - powershell为每个文件构建一个字符串来执行

java - 如何使用 Maven 在 Eclipse 中调试独立的 Java 程序?