以下实用程序类是否是线程安全的?

les*_*es2 3 java spring static multithreading thread-safety

首先让我们看看实用程序类(大多数javadoc已被删除,只是示例):

public class ApplicationContextUtils {

    /**
     * The application context; care should be taken to ensure that 1) this
     * variable is assigned exactly once (in the
     * {@link #setContext(ApplicationContext)} method, 2) the context is never
     * reassigned to {@code null}, 3) access to the field is thread-safe (no race
     * conditions can occur)
     */
    private static ApplicationContext context = null;

    public static ApplicationContext getContext() {

    if (!isInitialized()) {
        throw new IllegalStateException(
            "Context not initialized yet! (Has the "
                + "ApplicationContextProviderBean definition been configured "
                + "properly and has the web application finished "
                + "loading before you invoked this method?)");
    }

    return context;
    }

    public static boolean isInitialized() {
    return context == null;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(final String name, final Class<T> requiredType) {
    if (requiredType == null) {
        throw new IllegalArgumentException("requiredType is null");
    }
    return (T) getContext().getBean(name, requiredType);
    }

    static synchronized void setContext(final ApplicationContext theContext) {

    if (theContext == null) {
        throw new IllegalArgumentException("theContext is null");
    }

    if (context != null) {
        throw new IllegalStateException(
            "ApplicationContext already initialized: it cannot be done twice!");
    }

    context = theContext;
    }

    private ApplicationContextUtils() {
    throw new AssertionError(); // NON-INSTANTIABLE UTILITY CLASS
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,有以下辅助Spring托管bean实际调用'setContext'方法:

public final class ApplicationContextProviderBean implements
    ApplicationContextAware {

    public void setApplicationContext(
        final ApplicationContext applicationContext) throws BeansException {
    ApplicationContextUtils.setContext(applicationContext);
    }
}
Run Code Online (Sandbox Code Playgroud)

应用程序启动后,Spring将调用setApplicationContext方法一次.假设nincompoop之前没有调用ApplicationContextUtils.setContext(),那应该锁定对实用程序类中上下文的引用,允许调用getContext()成功(意味着isInitialized()返回true).

我只是想知道这个类是否违反了良好编码实践的任何原则,特别是线程安全(但是其他的愚蠢行为是受欢迎的).

感谢您帮助我成为更好的程序员,StackOverflow!

此致,LES

PS我没有说明为什么我需要这个实用程序类 - 让我确实有合法的需要从应用程序中的任何地方的静态上下文中访问它(当然,在加载Spring上下文之后).

eri*_*son 8

不,这不是线程安全的.

对于context通过读取该变量的线程,不保证对类变量的写入可见getContext().

至少,宣布contextvolatile.理想情况下,重新定义contextAtomicReference,通过这样的调用设置:

if(!context.compareAndSet(null, theContext))
  throw new IllegalStateException("The context is already set.");
Run Code Online (Sandbox Code Playgroud)

这是一个更完整的例子:

public class ApplicationContextUtils {

  private static final AtomicReference<ApplicationContext> context = 
    new AtomicReference<ApplicationContext>();

  public static ApplicationContext getContext() {
    ApplicationContext ctx = context.get();
    if (ctx == null)
      throw new IllegalStateException();
    return ctx;
  }

  public static boolean isInitialized() {
    return context.get() == null;
  }

  static void setContext(final ApplicationContext ctx) {
    if (ctx == null) 
      throw new IllegalArgumentException();
    if (!context.compareAndSet(null, ctx))
      throw new IllegalStateException();
  }

  public static <T> T getBean(final String name, final Class<T> type) {
    if (type == null) 
      throw new IllegalArgumentException();
    return type.cast(getContext().getBean(name, type));
  }

  private ApplicationContextUtils() {
    throw new AssertionError();
  }

}
Run Code Online (Sandbox Code Playgroud)

请注意,除了线程安全性之外,这还提供了类型安全性,利用了Class传递给getBean()方法的实例.

我不确定你打算如何使用这种isInitialized()方法; 这对我来说似乎没什么用处,因为一旦你打电话给它,情况就会发生变化,你就没有好的方法得到通知.

  • `getContext()`可以在**"调用`setContext()`之后失败**".没有内存障碍,你不能说一个线程何时看到另一个线程的动作.换句话说,如果没有规范中定义的*before-before*关系,"之前","期间"和"之后"之类的词语毫无意义. (5认同)
  • @ LES2:正确,完全正确.如果愿意,JVM完全有权进行此类观察.它可能永远不会发生,但这正是当您从客户端切换到服务器VM,或从Hotspot切换到JRockit时可以停止工作的那种东西,或者您的方法被调用1,000,001次触发某种JIT重新编译,管他呢.至少应该是挥发性的. (2认同)