1

列挙型の valueOf メソッドの簡単なフェールセーフ実装を提供する小さなヘルパー クラスを実装しました。これは、値が見つからない場合、例外ではなく null を返すことを意味します。

コードは次のとおりです。

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;

import java.io.Serializable;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * <p>
 * This permits to easily implement a failsafe implementation of the enums's valueOf method
 * </p>
 *
 * <p>
 * Basic usage exemple on an enum class called MyEnum:
 * FailSafeValueOf.get(MyEnum.class).valueOf("EnumName");
 * </p>
 *
 * @author Sebastien Lorber <i>(lorber.sebastien@gmail.com)</i>
 */
public class FailSafeValueOf<T extends Enum<T>> implements Serializable {

    /**
     * This will cache a FailSafeValueOf for each enum so that we do not need to recompute a map each time
     */
    private static final Map< Class<? extends Enum<?>> , FailSafeValueOf<? extends Enum<?>> >  CACHE = Maps.newHashMap();


    private final Map<String,T> nameToEnumMap;
    private FailSafeValueOf(Class<T> enumClass) {
        Map<String,T> map = Maps.newHashMap();
        for ( T value : EnumSet.allOf(enumClass)) {
            map.put( value.name() , value);
        }
        this.nameToEnumMap = ImmutableMap.copyOf(map);
    }

    /**
     * Returns the value of the given enum element
     * If the element is not found, null will be returned, and no exception will be thrown
     * @param enumName
     * @return
     */
    public T valueOf(String enumName) {
        return nameToEnumMap.get(enumName);
    }


    /**
     * Get a failsafe value of implementation for a given enum
     * @param enumClass
     * @param <U>
     * @return
     */
    public static <U extends Enum<U>> FailSafeValueOf<U> get(Class<U> enumClass) {
        FailSafeValueOf<U> fsvo = (FailSafeValueOf<U>)CACHE.get(enumClass);
        if ( fsvo == null ) {
            synchronized (FailSafeValueOf.class) {
                fsvo = (FailSafeValueOf<U>)CACHE.get(enumClass);
                if ( fsvo == null ) {
                    fsvo = new FailSafeValueOf<U>(enumClass);
                    CACHE.put(enumClass,fsvo);
                }
            }
        }
        return fsvo;
    }

}

アクセスごとに新しい FailSafeValueOf を作成する (少しの) オーバーヘッドが必要ないため、既にビルド済みの FailSafeValueOf インスタンスにアクセスした列挙型ごとに保持するキャッシュを作成しました。

私は並行性の処理に慣れていません。FailSafeValueOf が不変であり、FailSafeValueOf の 2 つの異なるインスタンスが同じ列挙型の get メソッドによって返される可能性がある場合、同時アクセスは大きな問題にはなりません。しかし、スレッドセーフの私の実装がその方法であるかどうか、そしてそれが本当にスレッドセーフであるかどうかを知りたいですか? (主に学習目的)

しばらくすると、すべての FailSafeValueOf がキャッシュに作成され、同時スレッドが get メソッドに入るのを禁止する必要がないため、メソッドを同期させたくありません。

だから私が作ったのは、最初にキャッシュミスがあるかどうかをチェックしてから、アトミックに同期されたブロックを作成することです:キャッシュを再度チェックし、最終的にインスタンスを作成します。それはスレッドセーフであり、そのような必要性のために行う方法はありますか?

ちなみに、enum は値の数が少ないことがよくあります。そのような場合、HashMap は適切な構造ですか? キャッシュを使用する代わりに、EnumSet を反復処理して適切な値を取得する方が高速ですか?


編集:

Guava チームが Optional を返すメソッド Enums.getIfPresent() をリリースしたため、私のクラスはあまり役に立たないことに注意してください。

4

3 に答える 3

2

私は使うだろう

private static final Cache<Class<?>, FailSafeValueOf<?>> CACHE
    = CacheBuilder.newBuilder().build(
        new CacheLoader<Class<?>, FailSafeValueOf<?>>() {
            @Override
            public FailSafeValueOf<?> load(Class<?> key) throws Exception {
                return new FailSafeValueOf(key);
            }
    });

これにより、最小限の作業で最大限の柔軟性が得られます。さらに、正しく高速に動作することを確信できます。

于 2012-05-26T10:16:33.343 に答える
2

メソッドを同期していないため、クラスはスレッドセーフではありませんCACHE.get()。これは、 がクラスではなくをMaps.newHashMap()返すことを前提としています。これは、次のコード スニペットで確認できます。HashMapConcurrentHashMap

    // you need to move your synchronized block out to here
    FailSafeValueOf<U> fsvo = (FailSafeValueOf<U>)CACHE.get(enumClass);
    if ( fsvo == null ) {
        synchronized (FailSafeValueOf.class) {
           ...
           CACHE.put(enumClass,fsvo);
        }
    }

そのメソッドが頻繁に呼び出される場合は、を移動するか、 を使用するように切り替える必要がありsynchronizedますCACHE.get(...)ConcurrentHashMap問題はHashMap、現在のスレッドがそれを読み取っている間に、別のスレッドによって が更新されることです。これは、競合状態のために簡単に問題を引き起こす可能性があります。

少し異なりますが、このような同期から身を守ろうとすることの難しさを理解するために、クラスの「Double Check Locking」のドキュメントも参照する必要があります。

最後に、CACHEそのロックの粒度が本当に必要でない限り、推奨されないクラスではなくオブジェクトで同期します。

于 2012-05-25T12:46:23.843 に答える
2

これには問題があります:

synchronized (FailSafeValueOf.class)

プライベート メンバーでのみ同期する必要があります。クラス オブジェクトはパブリックにアクセス可能であり、他のコードもそれをロックすることを選択する可能性があり、デッドロックが発生する可能性があります。

また、同時取得を許可する正しい解決策は、ロックをスキップするのではなく、リーダー/ライター ロックです。現在のコードが書き込み中に読み取られると、あらゆる種類の悲しみが生じる可能性があります。

于 2012-05-25T12:50:52.627 に答える