我想实现一个通过字符串值获取 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
的引用。但为什么调用innerMap
的get(symbol)
后会得到null
呢?
请帮忙,这让我发疯一整天!
最佳答案
问题出在线路上
List<Boolean> resultList = new 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/