Я хочу реализовать утилиту, получающую объект Enum по его строковому значению. Вот моя реализация.
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
. Но почему он наберет null
после вызова get(symbol)
из innerMap
?
Пожалуйста, помогите, это сводит меня с ума весь день!