私は@maaartinusの回答を受け入れましたが、質問の背後にある状況と興味深いウサギの穴について、別の「回答」を書いてみたかったのです。
TL; DR - thread safeで、グァバのEnums.ifPresent
とは違って同期しません使用するJavaのEnum.valueOf
。大部分の場合、おそらくそれは問題ではありません。
長い物語:
私は軽量のJavaスレッドQuasar Fibersを利用してコードベースに取り組んでいます。 Fibersのパワーを活用するために、コードはJava/OSスレッドに多重化されるため、実行されるコードは主に非同期で非ブロッキングにする必要があります。個々のファイバーが基礎となるスレッドを「ブロック」しないことが非常に重要になります。基底のスレッドがブロックされていると、実行されているすべてのファイバーがブロックされ、パフォーマンスが大幅に低下します。グアバのEnums.ifPresent
は、これらのブロッカーの1つであり、私はそれが避けられ得ると確信しています。
最初は、無効な列挙値にnull
を返すため、GuavaのEnums.ifPresent
を使用しました。 JavaのEnum.valueOf
と異なり、IllegalArgumentException
(これは私の好みではnull値よりも優先度が低い)です。ここ
列挙型に変換する種々の方法を比較粗ベンチマークである:
- Javaの
Enum.valueOf
null
- グアバの
Enums.ifPresent
- アパッチ・コモンズラング
EnumUtils.getEnum
- アパッチ・コモンズラング3を返すように
IllegalArgumentException
を引くとEnumUtils.getEnum
- My Own Custom Immuta BLEマップ検索
注:
- Apacheの共通ラング3は、Javaの
Enum.valueOf
ボンネットの下に使用していますので、Apacheの共通ラングの
- 以前のバージョンと同じですグアバに非常によく似た
WeakHashMap
ソリューションを使用していますが、い同期を使用しないでください。彼らは安価な読み込みとより高価な書き込みを好む(私の膝の反動反応はグアバがそれをしたはずだと言っている)
- 無効な列挙値を扱うときに
IllegalArgumentException
をスローするJavaの決定には、投げ捨て/例外のキャッチは無料ではありません。
- グアバは同期
ベンチマークセットアップ使用していますここでの唯一の方法である:10件のスレッド
の固定されたスレッドプールは、列挙型 を変換するために、100KのRunnableタスクを送信して
- が
ExecutorService
を使用しています
- 各Runnableタスクは100個の列挙型を変換します
- 列挙型を変換する各メソッドは、1000万個の文字列を変換します(100K x 100 )の実行から
ベンチマーク結果:
Convert valid enum string value:
JAVA -> 222 ms
GUAVA -> 964 ms
APACHE_COMMONS_LANG -> 138 ms
APACHE_COMMONS_LANG3 -> 149 ms
MY_OWN_CUSTOM_LOOKUP -> 160 ms
Try to convert INVALID enum string value:
JAVA -> 6009 ms
GUAVA -> 734 ms
APACHE_COMMONS_LANG -> 65 ms
APACHE_COMMONS_LANG3 -> 5558 ms
MY_OWN_CUSTOM_LOOKUP -> 92 ms
これらの数値は、塩の重い穀物で撮影する必要があり、他の要因に応じて変化します。しかし、彼らはFibersを使ってコードベースのためのJavaのソリューションに行くと結論づけてくれました。
ベンチマークコード:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Enums;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
public class BenchmarkEnumValueOf {
enum Strategy {
JAVA,
GUAVA,
APACHE_COMMONS_LANG,
APACHE_COMMONS_LANG3,
MY_OWN_CUSTOM_LOOKUP;
private final static ImmutableMap<String, Strategy> lookup;
static {
Builder<String, Strategy> immutableMapBuilder = ImmutableMap.builder();
for (Strategy strategy : Strategy.values()) {
immutableMapBuilder.put(strategy.name(), strategy);
}
lookup = immutableMapBuilder.build();
}
static Strategy toEnum(String name) {
return name != null ? lookup.get(name) : null;
}
}
public static void main(String[] args) {
final int BENCHMARKS_TO_RUN = 1;
System.out.println("Convert valid enum string value:");
for (int i = 0; i < BENCHMARKS_TO_RUN; i++) {
for (Strategy strategy : Strategy.values()) {
runBenchmark(strategy, "JAVA", 100_000);
}
}
System.out.println("\nTry to convert INVALID enum string value:");
for (int i = 0; i < BENCHMARKS_TO_RUN; i++) {
for (Strategy strategy : Strategy.values()) {
runBenchmark(strategy, "INVALID_ENUM", 100_000);
}
}
}
static void runBenchmark(Strategy strategy, String enumStringValue, int iterations) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
long timeStart = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
executorService.submit(new EnumValueOfRunnable(strategy, enumStringValue));
}
executorService.shutdown();
try {
executorService.awaitTermination(1000, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long timeDuration = System.currentTimeMillis() - timeStart;
System.out.println("\t" + strategy.name() + " -> " + timeDuration + " ms");
}
static class EnumValueOfRunnable implements Runnable {
Strategy strategy;
String enumStringValue;
EnumValueOfRunnable(Strategy strategy, String enumStringValue) {
this.strategy = strategy;
this.enumStringValue = enumStringValue;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
switch (strategy) {
case JAVA:
try {
Enum.valueOf(Strategy.class, enumStringValue);
} catch (IllegalArgumentException e) {}
break;
case GUAVA:
Enums.getIfPresent(Strategy.class, enumStringValue);
break;
case APACHE_COMMONS_LANG:
org.apache.commons.lang.enums.EnumUtils.getEnum(Strategy.class, enumStringValue);
break;
case APACHE_COMMONS_LANG3:
org.apache.commons.lang3.EnumUtils.getEnum(Strategy.class, enumStringValue);
break;
case MY_OWN_CUSTOM_LOOKUP:
Strategy.toEnum(enumStringValue);
break;
}
}
}
}
}
は 'それは重いパフォーマンスの低下を招くと思いません'必ずしもそうではありません?。特に私は、 "重い"はフラットなノーだと思う。同期ブロック内のコードは、静的(グローバル)キャッシュオブジェクトにアクセスするように見えます。そのため、同期が必要になる可能性があります。私は、Javaの 'valueOf()'メソッドが* immutable *オブジェクトにアクセスするため、ミューテックスやその他のメモリの可視性操作を必要としないと考えています。 – markspace
あなたの最後の文章で二重チェックロックを考えているようです。現代のJVMでは、無条件の同期が非常に高速です。私はそれがメモリバリアを作成するためにここで使用されていると思います。そのため、 '定数 'が読み込まれると、他のすべてのスレッドがすぐにそれを見ることが保証されます。 –
ああ、良いキャッチ。 'enumがキャッシュに存在する場合、ロックせずに取得できます。いいえいいえいいえいいえ。 Javaの可視性(スレッド間)は、ライターとリーダーの両方によるロックが必要であり、どちらも同じロックを使用する必要があります。 Javaメモリモデルではそれ以外のものは壊れています。読み込みは、書き込みと同様にロックが必要です。これが不変性が大きな問題である理由です。読者がメモリの可視性に特別なことをする必要はありません。詳細については、Brian Goetzの[Java Concurrency in Practice](http://jcip.net/)を参照してください。 – markspace