枚举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它肯定是安全的。