Javaで値を取得する際に、スレッドセーフなHashMapの遅延初期化を実装する方法?

ハンク:

私は、その文字列値で列挙型オブジェクトを取得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

これは、1つのスレッドだけのコード行を実行することを意味します:

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

だから、初期化コードは、1つのスレッドによって実行されなければなりません。

奇妙なことには、いくつかのスレッドの結果がなる、あるnullコード行を実行した後:

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

何NullPointerExceptionがありませんので、stringEnumMap.get(enumClassName)の参照を返す必要がありますinnerMapしかし、なぜそれが得られますnull呼び出した後get(symbol)innerMap

ヘルプ、それは一日狂気私を運転してください!

Smbchde:

問題は、ラインが原因であります

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

以下からのLinkedListのは、JavaDoc

この実装は同期化されていないことに注意してください。複数のスレッドが同時にリンクリストにアクセスする場合は、リスト構造のスレッドの修正の少なくとも1つは、それが外部で同期をとる必要があります。(構造的修飾は、一つまたは複数の要素を追加または削除する操作であり、単に要素の値を設定する構造変更はない。)これは、典型的にそのようなオブジェクトが存在しない天然list.Ifをカプセル化するいくつかのオブジェクトに同期させることによって達成されます。 、リストはCollections.synchronizedListmethodを使って「ラップ」する必要があります。これは最高のリストへの偶発的な非同期アクセスを防ぐために、作成時に行われます。 List list = Collections.synchronizedList(new LinkedList(...));

LinkedListスレッドセーフではなく、予期せぬ動作が起こる時にadd操作。原因となるresultListサイズより少ないスレッド数よりも、ひいては予想カウントは、結果の数よりも少ないです。
正しい結果を得るには、追加Collections.synchronizedList提案されているよう。

もし実装が細かいですが、私はあなたが簡単かつ堅牢なソリューションのためのマットTimmermans答えに従うことをお勧めします。

おすすめ

転載: http://43.154.161.224:23101/article/api/json?id=17999&siteId=1