枚举valueOf()的线程安全

时间:2012-08-16 12:33:32

标签: java enums thread-safety value-of

这是“冗长 - 如果或转换”困境的变体......

考虑使用静态方法的多线程应用程序,该方法包含一个长(十几个条件)if语句,它检查对象的类型并相应地返回一个值,例如

public static String checkType(Class<?> type)
{
    if (type == A.class)
    {
        return aString;
    }
    else if (type == B.class)
    {
        return bString;
    }
    ...
    else if (type == z.class)
    {
        return zString;
    }
}

显然,switch语句不能直接应用于此,因此常见的模式是enum并调用其valueOf(),即执行类似

的操作
public enum Strings
{
    A(aString), B(bString), ..., Z(zString)

    private final String value;

    private Strings(String value)
    {
        this.value = value;
    }

    public String value()
    {
        return this.value;
    }
}

因此,checkType()可以重写为

public static String checkType(Class<?> type)
{
    return Strings.valueOf(getActualTypeName(type.getClass().getName())).value();
}

null方法中对生产代码中添加的getActualTypeName()值进行适当的检查,对非原始类型进行一些String处理,以从"class java.lang.Long"等字符串中检索实际的类型名称(对于基元,getName()方法返回预期的字符串,例如“long"”。

但是,如果valueOf()不是线程安全的,那么这在并发环境中不起作用。这同样适用于使用(普通)Map对象,可能这两个替代方案是相同模式的变体,因为enum.valueOf()显然基于

Enum.valueOf(Class<T> enumType, String name)

调用

enumType.enumConstantDirectory().get(name);

Class.java班。

每次调用enumConstantDirectory()方法都会返回一个新的HashMap,它是根据values()数组的副本创建的。

这是线程安全吗?

3 个答案:

答案 0 :(得分:4)

我找不到enum.valueOf(String)不能保证线程安全的任何原因:

  • 字符串是不可变的,因此当valueOf完成其工作
  • 时,参数不能变异
  • valueOf检查参数与枚举常量的名称,它们都是静态的和最终的

是什么让你认为enum.valueOf()不是线程安全的?

修改

valueOf调用:

T result = enumType.enumConstantDirectory().get(name);

其中enumType是您的枚举类。

enumConstantDirectory()使用以下模式:

Map<String, T> enumConstantDirectory() {
    if (enumConstantDirectory == null) {
        T[] universe = getEnumConstantsShared();
        if (universe == null)
            throw new IllegalArgumentException(
                getName() + " is not an enum type");
        Map<String, T> m = new HashMap<>(2 * universe.length);
        for (T constant : universe)
            m.put(((Enum<?>)constant).name(), constant);
        enumConstantDirectory = m;
    }
    return enumConstantDirectory;
}

其中enumConstantDirectory是易变变量:

private volatile transient Map<String, T> enumConstantDirectory = null;

想象一下在该方法中并发到达的一个线程:

  • 如果enumConstantDirectory为空(此处没有可见性问题,因为它是易失性的),它将构造映射并将其分配给该变量。由于易失性保证,从那个时间点开始,所有其他线程都将完全构建地图。
  • 如果另一个线程同时到达该方法并且还观察到enumConstantDirectory的空值,它将重新创建地图并安全地再次发布

最糟糕的情况是2个线程可能使用2个不同的地图(不同的实例),但它们的内容是相同的,因此不会引起任何问题。

底线:线程无法看到一半构造的地图,因为地图构造是在局部变量上完成的,该变量被分配给volatile变量之后,它已被填充。

答案 1 :(得分:3)

没有理由认为Enum.valueOf()不是线程安全的。它不会改变任何东西,它只是在实际的enum类中访问状态,而且实际上是最终的。

如果这个方法 是非线程安全的,我认为javadocs中会有这样的内容。

答案 2 :(得分:1)

可能我错了,但似乎这里有一个微妙的问题:

public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                       String name) {
     T result = enumType.enumConstantDirectory().get(name);
     if (result != null)
           return result;
     if (name == null)
           throw new NullPointerException("Name is null");
     throw new IllegalArgumentException(
                     "No enum constant " + enumType.getCanonicalName() + "." + name);
}  

这是valueOf的代码。它使用传入的enumType创建一个包含常量的内部HashMap,代码不是sychronized
这里似乎有一个微妙的问题:T result = enumType.enumConstantDirectory().get(name);
enumConstantDirectory()检查enumConstantDirectory == null但未同步,以便创建HashMap。也许副作用并不重要(我不知道Class存储什么信息),但无论如何只要enumType未在您的应用程序代码中共享,它肯定是安全的