gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 23:41:25 25 4
gpt4 key购买 nike

我想实现一个通过字符串值获取 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/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com