Ich habe @maaartinus Antwort angenommen, aber wollte eine separate "Antwort" über die Umstände hinter der Frage und dem interessanten Kaninchenloch schreiben, zu dem es mich führt.
tl; dr - Verwendung von Java Enum.valueOf
die thread safe ist und Synchronisierung nicht im Gegensatz zu Guava ist Enums.ifPresent
. Auch in den meisten Fällen spielt es wahrscheinlich keine Rolle.
Lange Geschichte:
Ich bin auf einer Code-Basis arbeiten, die Java-Leichtgewichtler Threads nutzt Quasar Fibers. Um die Leistungsfähigkeit von Fibers zu nutzen, sollte der von ihnen ausgeführte Code hauptsächlich async und nicht blockierend sein, da Fasern zu Java/OS-Threads gemultiplext sind. Es wird sehr wichtig, dass einzelne Fasern den darunter liegenden Thread nicht "blockieren". Wenn der zugrundeliegende Thread blockiert ist, werden alle darauf laufenden Fibers blockiert und die Leistung wird erheblich beeinträchtigt. Guavas Enums.ifPresent
ist einer dieser Blocker und ich bin mir sicher, dass es vermieden werden kann.
Zuerst begann ich Guava Enums.ifPresent
zu verwenden, weil es null
auf ungültige Enum-Werte zurückgibt. Im Gegensatz zu Javas Enum.valueOf
, die IllegalArgumentException
wirft (was meiner Meinung nach weniger vorzuziehen ist als ein Nullwert).
Hier ist eine grobe Benchmark verschiedene Verfahren zur Umwandlung in Aufzählungen Vergleich:
- Java
Enum.valueOf
mit IllegalArgumentException
fangen null
- Guava des
Enums.ifPresent
- Apache Commons Lang
EnumUtils.getEnum
- Apache Commons Lang 3 zurück
EnumUtils.getEnum
- Meine eigene benutzerdefinierte Immuta ble Karte Lookup
Hinweise:
- Apache Commons Lang 3 verwendet Java
Enum.valueOf
unter der Haube und sind daher identisch
- frühere Version von Apache Commons Lang verwendet eine sehr ähnliche
WeakHashMap
Lösung Guava aber tut verwende keine Synchronisation. Sie bevorzugen billige Lesevorgänge und teurere Schreibvorgänge (meine Reaktion auf den Kniestoß sagt, dass Guava es hätte tun sollen)
- Javas Entscheidung,
IllegalArgumentException
zu werfen, ist wahrscheinlich mit geringen Kosten verbunden, wenn es um ungültige Enum-Werte geht. Das Werfen/Fangen von Ausnahmen ist nicht kostenlos.
- Guava ist die einzige hier Methode, die Synchronisation
Benchmark-Konfiguration verwendet:
- verwendet ein
ExecutorService
mit einem festen Thread-Pool von 10 Fäden
- einreicht 100K Runnable Aufgaben Aufzählungen konvertieren
- Jeder ausführbare Task konvertiert 100 Enums
- Jede Methode zum Konvertieren von Enums konvertiert 10 Millionen Strings (100K x 100
)
Benchmark-Ergebnisse von einem Lauf:
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
Diese Zahlen sollten mit einem schweren Körnchen Salz genommen werden und wird von anderen Faktoren ändern sich abhängig. Aber sie waren gut genug für mich, um mit der Lösung von Java für die Codebasis mit Fibers zu gehen.
Benchmark-Code:
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;
}
}
}
}
}
'Wäre das nicht eine schwere Leistungseinbußen entstehen' Nicht unbedingt?. Insbesondere denke ich "schwer" ist eine flache Nr. Der Code im synchronisierten Block sieht so aus, als ob er auf ein statisches (globales) Cache-Objekt zugreift, daher wird wahrscheinlich die Synchronisation dafür benötigt. Ich nehme an, dass die 'valueOf()' Methode von Java auf ein * immutable * Objekt zugreift und daher keine Mutex oder andere Speicher Sichtbarkeitsoperationen benötigt. – markspace
Es klingt, als ob Sie in Ihrem letzten Satz an eine doppelte Überprüfung denken. Bei modernen JVMs ist die unangefochtene Synchronisation sehr schnell. Ich denke, dass es hier verwendet wird, um eine Speicherbarriere zu schaffen, so dass, wenn "Konstanten" gefüllt sind, alle anderen Threads es garantiert sofort sehen werden, aber ich werde jemanden mit mehr Fachwissen antworten lassen. –
Oh richtig, guter Fang. 'Wenn Enum im Cache vorhanden ist, kann es ohne Sperren abgerufen werden. Nein Nein Nein Nein Nein Nein. Sichtbarkeit (zwischen Threads) in Java erfordert eine Sperre sowohl von einem Schreiber als auch von einem Leser, und beide müssen dieselbe Sperre verwenden. Alles andere ist im Java-Speichermodell unterbrochen. Lesevorgänge müssen wie bei Schreibvorgängen gesperrt werden. Dies ist der Grund, warum Unveränderlichkeit eine große Sache ist, es erfordert NICHT, dass der Leser etwas Besonderes für die Sichtbarkeit des Speichers tut. Bitte lesen Sie Brian Goetz [Java Parallelität in der Praxis] (http://jcip.net/) für Details. – markspace