Guava的Enums.ifPresent(Class, String)
来电Enums.getEnumConstants
:
@GwtIncompatible // java.lang.ref.WeakReference
static <T extends Enum<T>> Map<String, WeakReference<? extends Enum<?>>> getEnumConstants(
Class<T> enumClass) {
synchronized (enumConstantCache) {
Map<String, WeakReference<? extends Enum<?>>> constants = enumConstantCache.get(enumClass);
if (constants == null) {
constants = populateCache(enumClass);
}
return constants;
}
}
为什么需要同步块?不会导致重度性能损失吗? Java Enum.valueOf(Class, String)
似乎不需要一个。如果确实需要同步,为什么这么低效呢?人们希望如果枚举存在于缓存中,则可以在不锁定的情况下检索它。只有在需要填充缓存时才锁定。
供参考:Maven Dependency
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.2-jre</version>
</dependency>
编辑:通过锁定我指的是双重检查锁。
答案 0 :(得分:1)
我猜,原因很简单enumConstantCache
是WeakHashMap
,这不是线程安全的。
同时写入缓存的两个线程最终可能会出现无限循环或类似情况(至少在HashMap
发生了这种情况,就像我多年前尝试过的那样)。
我想,你可以使用DCL,但它可能不值得(如评论中所述)。
如果确实需要同步,为什么这么低效呢?人们希望如果枚举存在于缓存中,则可以在不锁定的情况下检索它。只有在需要填充缓存时才锁定。
这可能太棘手了。要使用volatile
进行可见性,您需要一个易失性读取与易失性写入配对。通过将enumConstantCache
声明为volatile
而不是final
,您可以轻松获得易失性读取。易失性写入更棘手。像
enumConstantCache = enumConstantCache;
可能会奏效,但我对此并不确定。
10个线程,每个线程都必须将String值转换为Enums,然后执行一些任务
地图访问通常比使用获得的值更快地进行访问,因此我想,您需要更多线程来解决问题。
与HashMap
不同,WeakHashMap
需要执行一些清理(称为expungeStaleEntries
)。即使在get
(通过getTable
)也可以执行此清理。因此get
是一项修改操作,您真的不想同时执行它。
请注意,在没有同步的情况下阅读WeakHashMap
意味着在没有锁定的情况下执行变异,这是完全错误的that's not only theory。
您需要WeakHashMap
的{{1}}自己的版本在get
中执行无变异(这很简单),并且在读取期间由不同的线程(可能会或可能会)保证一些明智的行为不可能)。
我想,像SoftReference<ImmutableMap<String, Enum<?>>
这样的某些重新加载逻辑可以很好地工作。
答案 1 :(得分:1)
我已经接受了@maaartinus的答案,但是想写一个单独的“答案”,说明问题背后的情况以及它带给我的有趣的兔子洞。
tl; dr - 使用Java
Enum.valueOf
,thread safe并且不像Guava的Enums.ifPresent
那样同步。同样在大多数情况下,它可能无关紧要。
长篇故事:
我正在开发一个利用轻量级java线程Quasar Fibers的代码库。为了利用Fibers的强大功能,它们运行的代码应该主要是异步和非阻塞,因为Fibers被多路复用到Java / OS线程。单个Fibers不会“阻塞”底层线程变得非常重要。如果底层线程被阻塞,它将阻止在其上运行的所有光纤并且性能显着下降。番石榴Enums.ifPresent
是其中一个阻挡者,我确信它可以避免。
最初,我开始使用Guava的Enums.ifPresent
,因为它会在无效的枚举值上返回null
。与引用Enum.valueOf
的Java IllegalArgumentException
不同(根据我的口味,它比空值更不可取)。
以下是比较各种转换为枚举的方法的粗略基准:
Enum.valueOf
抓住IllegalArgumentException
返回null
Enums.ifPresent
EnumUtils.getEnum
EnumUtils.getEnum
注意:
Enum.valueOf
,因此是相同的WeakHashMap
解决方案,但不使用同步。他们喜欢便宜的阅读和更昂贵的写作(我的膝盖反射说反应是Guava应该如何做到的)IllegalArgumentException
可能会产生相关的小成本。投掷/捕获异常不是免费的。基准设置:
ExecutorService
跑步的基准测试结果:
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;
}
}
}
}
}