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

时间:2009-11-11 19:51:49

标签: java multithreading spring static 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
    }
}

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

public final class ApplicationContextProviderBean implements
    ApplicationContextAware {

    public void setApplicationContext(
        final ApplicationContext applicationContext) throws BeansException {
    ApplicationContextUtils.setContext(applicationContext);
    }
}

Spring将在应用程序启动后调用setApplicationContext方法一次。假设一个傻子没有以前称为ApplicationContextUtils.setContext(),这应该在参照在工具类的上下文锁,允许的getContext()来成功(意味着将IsInitialized()返回true)的呼叫。

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

感谢您帮助我成为更优秀的程序员StackOverflow!

此致 LES

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

2 个答案:

答案 0 :(得分:8)

没有。它不是线程安全的。

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

至少,将context声明为volatile。理想情况下,将context重新定义为AtomicReference,通过以下呼叫进行设置:

if(!context.compareAndSet(null, theContext))
  throw new IllegalStateException("The context is already set.");

这是一个更完整的例子:

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

}

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

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

答案 1 :(得分:1)

Spring已经有一个名为ContextSingletonBeanFactoryLocator的类,它可以为您提供对ApplicationContext的静态访问。至少,使用这个类可能会省去你不必担心自定义方法是否是线程安全的问题。

然而,最初使用这个类有点令人困惑,因为有一点间接性。您可以查看this blog post,了解有关此通话如何运作的详细信息。