Thread safety of enum valueOf()

孤街浪徒 提交于 2019-12-08 06:46:25

问题


This is a variation of the perpertual "lengthy-if or switch" dilemma...

Consider a multithreaded application using a static method that contains a long (over a dozen conditions) if statement, which checks the type of an object and returns a value accordingly, i.e. something like

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;
    }
}

Obviously a switch statement is not directly applicable here, so a common pattern is to have an enum and call its valueOf(), i.e. do something like

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;
    }
}

So, checkType() could be re-written as

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

with appropriate checks for null values added in production code and some String processing for non-primitive types, inside the getActualTypeName() method, to retrieve the actual type name from strings like "class java.lang.Long" (for primitives, the getName() method returns the expected string, e.g. "long").

However, if valueOf() is not thread-safe, this will not work in a concurrent environment. The same applies to using a (normal) Map object and probably these two alternatives are variants of the same pattern, since enum.valueOf() is apparently based on

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

which calls

enumType.enumConstantDirectory().get(name);

in the Class.java class.

The enumConstantDirectory() method, every time invoked, returns a new HashMap, created from a copy of the values() array.

Would that be thread safe?


回答1:


I can't find any reasons why enum.valueOf(String) would not be thread safe:

  • strings are immutable, so the argument can't be mutated while valueOf does its job
  • valueOf checks the argument vs. the names of the enum constants and they are all static and final

What makes you think that enum.valueOf() is not thread safe?

EDIT

valueOf calls:

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

where enumType is your enum class.

enumConstantDirectory() uses this pattern:

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;
}

where enumConstantDirectory is a volatile variable:

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

Imagine a thread arriving concurrently in that method:

  • if enumConstantDirectory is null (there is no visibility issue here because it is volatile), it will construct the map and assign it to that variable. Because of the volatile guarantees, all other threads, from that point in time, will see the map fully constructed.
  • if another thread arrives in the method at the same time and also observe a null value of enumConstantDirectory, it will recreate the map and safely publish it again

The worst case scenario here is that 2 threads could potentially be using 2 different maps (different instances) but their content will be the same so it would not cause any issues.

Bottom line: there is no way that a thread could see a map which is half constructed, because the map construction is done on a local variable, which is assigned to the volatile variable after it has been populated.




回答2:


There is no reason to suppose that Enum.valueOf() is not thread-safe. It does not mutate anything, and it is only accessing state in the actual enum class that is effectively final.

If this method was non-thread-safe, I think there would be something in the javadocs to say so.




回答3:


May be I am wrong but it seems there is a subtle issue here:

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);
}  

This is the code for valueOf. It uses the passed in enumType to create an internal HashMap with the constants and the code is not sychronized.
There seems a subtle issue here: T result = enumType.enumConstantDirectory().get(name);
The enumConstantDirectory() does a check for enumConstantDirectory == null but it is not synchronized, in order to create the HashMap. Perhaps the side-effects are not important (I don't know what info the Class stores) but in any case it is surely safe as long as enumType is not shared in your application code



来源:https://stackoverflow.com/questions/11987224/thread-safety-of-enum-valueof

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!