枚举valueOf()的线程安全
这是一种变形的“perful -if or switch”困境……
考虑使用静态方法的multithreading应用程序,该方法包含一个long(十几个条件) 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(); }
在getActualTypeName()
方法中对生成代码中添加的null
值和非原始类型的一些String处理进行适当的检查,以从"class java.lang.Long"
等字符串中检索实际的类型名称(对于基元, getName()
方法返回预期的字符串,例如“ long"
)。
但是,如果valueOf()
不是线程安全的,则在并发环境中不起作用。 这同样适用于使用(普通) Map
对象,可能这两个替代方案是相同模式的变体,因为enum.valueOf()
显然是基于
Enum.valueOf(Class enumType, String name)
哪个叫
enumType.enumConstantDirectory().get(name);
在Class.java
类中。
每次调用时, enumConstantDirectory()
方法都会返回一个新的HashMap
,它是从values()
数组的副本创建的。
那是线程安全吗?
我找不到任何理由为什么enum.valueOf(String)
不是线程安全的:
- 字符串是不可变的,因此当
valueOf
完成其工作时,参数不能被突变 - valueOf检查参数与枚举常量的名称,它们都是静态和最终的
是什么让你认为enum.valueOf()
不是线程安全的?
编辑
valueOf调用:
T result = enumType.enumConstantDirectory().get(name);
其中enumType
是你的枚举类。
enumConstantDirectory()使用以下模式:
Map enumConstantDirectory() { if (enumConstantDirectory == null) { T[] universe = getEnumConstantsShared(); if (universe == null) throw new IllegalArgumentException( getName() + " is not an enum type"); Map m = new HashMap<>(2 * universe.length); for (T constant : universe) m.put(((Enum>)constant).name(), constant); enumConstantDirectory = m; } return enumConstantDirectory; }
其中enumConstantDirectory
是一个volatile变量:
private volatile transient Map enumConstantDirectory = null;
想象一下在该方法中并发到达的线程:
- 如果
enumConstantDirectory
为null(此处没有可见性问题,因为它是易失性的),它将构造映射并将其分配给该变量。 由于易失性保证,从那个时间点开始,所有其他线程将完全构建地图。 - 如果另一个线程同时到达方法并且还观察到
enumConstantDirectory
的空值,它将重新创建地图并再次安全地发布它
这里最糟糕的情况是2个线程可能使用2个不同的映射(不同的实例)但它们的内容将是相同的,因此它不会导致任何问题。
底线 :线程无法看到构造为一半的地图,因为地图构造是在局部变量上完成的,该局部变量在填充后分配给volatile变量。
没有理由认为Enum.valueOf()
不是线程安全的。 它不会改变任何东西,它只是在实际的enum
类中访问有效的最终状态。
如果这个方法是非线程安全的,我认为在javadocs中会有这样的说法。
可能是我错了,但似乎这里有一个微妙的问题:
public static > T valueOf(Class 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
它肯定是安全的。