1

これは、永続的な「長いifまたはスイッチ」のジレンマのバリエーションです...

長い(ダース以上の条件)ステートメントを含む静的メソッドを使用するマルチスレッドアプリケーションについて考えてみますif。このステートメントは、オブジェクトの型をチェックし、それに応じて値を返します。

public static String checkType(Class<?> type)
{
    if (type == A.class)
    {
        return aString;
    }
    else if (type == B.class)
    {
        return bString;
    }
    ...
    else if (type == z.class)
    {
        return zString;
    }
}

明らかに、switchステートメントはここでは直接適用できないため、一般的なパターンは、を持ってenumそれを呼び出すことvalueOf()です。つまり、次のようなことを行います。

public enum Strings
{
    A(aString), B(bString), ..., Z(zString)

    private final String value;

    private Strings(String value)
    {
        this.value = value;
    }

    public String value()
    {
        return this.value;
    }
}

したがって、次のcheckType()ように書き直すことができます

public static String checkType(Class<?> type)
{
    return Strings.valueOf(getActualTypeName(type.getClass().getName())).value();
}

実動コードに追加された値の適切なチェックと、メソッドnull内の非プリミティブ型の文字列処理を使用して、 (プリミティブの場合、メソッドは期待される文字列を返します。たとえば、 " )のgetActualTypeName()ような文字列から実際の型名を取得します。"class java.lang.Long"getName()long"

ただし、valueOf()がスレッドセーフでない場合、これは並行環境では機能しません。同じことが(通常の)Mapオブジェクトの使用にも当てはまり、おそらくこれら2つの選択肢は、enum.valueOf()明らかにに基づいているため、同じパターンのバリアントです。

Enum.valueOf(Class<T> enumType, String name)

これは

enumType.enumConstantDirectory().get(name);

Class.javaクラスで。

このenumConstantDirectory()メソッドは、呼び出されるたびに、配列HashMapのコピーから作成された新しいを返します。values()

それはスレッドセーフでしょうか?

4

3 に答える 3

5

enum.valueOf(String)スレッドセーフにならない理由がわかりません。

  • 文字列は不変であるため、引数を変更することはできませんvalueOf
  • valueOfは、引数と列挙型定数の名前をチェックし、それらはすべて静的で最終的なものです

enum.valueOf()スレッドセーフではないと思われる理由は何ですか?

編集

valueOf呼び出し:

T result = enumType.enumConstantDirectory().get(name);

enumType列挙型クラスはどこにありますか。

enumConstantDirectory()は次のパターンを使用します。

Map<String, T> enumConstantDirectory() {
    if (enumConstantDirectory == null) {
        T[] universe = getEnumConstantsShared();
        if (universe == null)
            throw new IllegalArgumentException(
                getName() + " is not an enum type");
        Map<String, T> m = new HashMap<>(2 * universe.length);
        for (T constant : universe)
            m.put(((Enum<?>)constant).name(), constant);
        enumConstantDirectory = m;
    }
    return enumConstantDirectory;
}

ここenumConstantDirectoryで、は揮発性変数です。

private volatile transient Map<String, T> enumConstantDirectory = null;

そのメソッドで同時に到着するスレッドを想像してみてください。

  • がnullの場合enumConstantDirectory(揮発性であるため、ここでは可視性の問題はありません)、マップを作成してその変数に割り当てます。揮発性の保証があるため、その時点以降、他のすべてのスレッドはマップが完全に構築されていることを確認します。
  • 別のスレッドが同時にメソッドに到着し、null値が観測された場合enumConstantDirectory、マップが再作成され、安全に再公開されます。

ここでの最悪のシナリオは、2つのスレッドが2つの異なるマップ(異なるインスタンス)を使用している可能性がありますが、それらのコンテンツは同じであるため、問題は発生しません。

結論:マップの作成はローカル変数で行われるため、スレッドは半分作成されたマップを見ることができません。ローカル変数は、データが入力されたに揮発性変数に割り当てられます。

于 2012-08-16T12:37:33.637 に答える
4

Enum.valueOf()それがスレッドセーフではないと考える理由はありません。何も変更せず、実際のenumクラスの状態にアクセスするだけで、事実上最終的なものになります。

このメソッドスレッドセーフでない場合は、javadocsにそのように言うことがあると思います。

于 2012-08-16T12:38:20.183 に答える
1

私は間違っているかもしれませんが、ここには微妙な問題があるようです:

public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                       String name) {
     T result = enumType.enumConstantDirectory().get(name);
     if (result != null)
           return result;
     if (name == null)
           throw new NullPointerException("Name is null");
     throw new IllegalArgumentException(
                     "No enum constant " + enumType.getCanonicalName() + "." + name);
}  

これはvalueOfのコードです。渡されたものを使用して定数をenumType含む内部を作成しますが、コードはではありません。 ここには微妙な問題があるようです。を作成するために、はチェックを行い ますが、同期されていません。おそらく副作用は重要ではありませんが(どの情報が保存されているかはわかりません)、いずれの場合も、アプリケーションコードで共有されていない限り、確実に安全です。HashMapsychronized
T result = enumType.enumConstantDirectory().get(name);
enumConstantDirectory()enumConstantDirectory == nullHashMapClassenumType

于 2012-08-16T12:47:27.193 に答える