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 是不可变的,并且同一枚举的 get 方法可以返回 2 个不同的 FailSafeValueOf 实例。但是我想知道我的线程安全实现是否可行,以及它是否真的是线程安全的?(主要用于学习目的)

我不想让我的方法同步,因为一段时间后,所有的FailSafeValueOf都在缓存中创建,不需要禁止并发线程进入get方法。

所以我所做的是首先检查是否有缓存未命中,然后创建一个自动同步的块:再次检查缓存并最终创建实例。它是线程安全的并且是满足这种需求的方法吗?

顺便说一句,枚举通常具有少量值。在这种情况下,HashMap 是一个合适的结构吗?迭代 EnumSet 并获得适当的值是否比使用缓存更快?


编辑:

请注意,我的课程不是很有用,因为 Guava 团队已经关联了一个 Enums.getIfPresent() 方法,它返回一个 Optional

4

3 回答 3

2

您的类不是线程安全的,因为您没有围绕该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);
        }
    }

如果经常调用该方法,您将需要移动synchronizedCACHE.get(...)切换到使用。ConcurrentHashMap问题是HashMap当当前线程正在读取它时,它会被另一个线程更新——这很容易因为竞争条件而导致问题。

尽管略有不同,但您还应该查看“双重检查锁定”类文档,以了解更多尝试将自己从同步中解救出来的困难。

最后,我将在CACHE对象而不是类上进行同步,除非您确实需要该锁定粒度,否则不推荐使用该类。

于 2012-05-25T12:46:23.843 回答
2

这是有问题的:

synchronized (FailSafeValueOf.class)

您应该只在私有成员上同步。类对象是可公开访问的,其他代码也可能选择锁定它,从而导致潜在的死锁。

此外,允许并发获取的正确解决方案是读写锁,而不是跳过锁。如果您当前的代码在编写时读取,您可能会感到各种悲伤。

于 2012-05-25T12:50:52.627 回答
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 回答