[英]Is my Java cache thread-safe and performant?
我实现了一个小帮助程序类,该类提供了枚举的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是不可变的并且对于同一枚举的get方法可以返回2个不同的FailSafeValueOf实例的情况下,并发访问可能不是一个大问题。 但是我想知道我的线程安全性实现方式是否可行,以及它是否真的是线程安全性的? (主要用于学习目的)
我不想使我的方法同步,因为过了一段时间,所有FailSafeValueOf都在缓存中创建,并且不需要禁止并发线程进入get方法。
因此,我要做的是先检查是否存在缓存未命中,然后创建一个将自动进行同步的块:再次检查缓存并最终创建实例。 它是线程安全的,并且是满足这种需求的方法吗?
顺便说一下,枚举通常具有少量的值。 在这种情况下,HashMap是否合适? 遍历EnumSet并获取适当的值而不是使用缓存是否更快?
编辑:
请注意,我的课程不是那么有用,因为Guava团队使用了Enums.getIfPresent()方法,该方法返回Optional
您的类不是线程安全的,因为您没有在CACHE.get()
方法周围进行同步。 假定Maps.newHashMap()
返回HashMap
而不是ConcurrentHashMap
类。 您可以在以下代码片段中看到此内容:
// 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);
}
}
如果经常调用该方法,则需要在CACHE.get(...)
周围移动synchronized
,或者切换到使用ConcurrentHashMap
。 问题在于,当当前线程正在从HashMap
读取HashMap
,该线程将被另一个线程更新-由于竞争条件,这很容易引起问题。
尽管稍有不同,但您还应该查看“双重检查锁定”类文档,以了解更多有关尝试像这样从同步中拯救自己的困难。
最后,除非您确实需要该锁粒度,否则我将在CACHE
对象而不是不推荐的类上进行同步。
这是有问题的:
synchronized (FailSafeValueOf.class)
您只应在私人成员上进行同步。 类对象是可公开访问的,其他代码也可能选择对其进行锁定,从而导致潜在的死锁。
另外,允许并发获取的正确解决方案是读写器锁,而不是跳过该锁。 如果您当前的代码是在编写时读取的,则可能会遇到各种各样的麻烦。
我会用
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);
}
});
在最少的工作量下为您提供最大的灵活性 。 此外,您可以肯定它可以正确且快速地运行。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.