2009-11-11 2 views
3

Сначала давайте посмотрим на класс полезности (наиболее 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 управляемый компонент, который на самом деле вызывает метод «SetContext»:

public final class ApplicationContextProviderBean implements 
    ApplicationContextAware { 

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

Весна вызовет метод setApplicationContext один раз после запуска приложения. Предполагая, что nincompoop ранее не назывался ApplicationContextUtils.setContext(), который должен блокировать ссылку на контекст в классе утилиты, что позволяет вызовам getContext() для успеха (это означает, что isInitialized() возвращает true).

Я просто хочу знать, нарушает ли этот класс какие-либо принципы хорошей практики кодирования, в частности, в отношении безопасности потоков (но можно найти другие глупости).

Спасибо, что помогли мне стать лучшим программистом, StackOverflow!

С уважением, LES

P.S. Я не заходил в , почему Мне нужен этот класс утилиты - давайте достаточно, чтобы у меня действительно была законная необходимость доступа к нему из статического контекста в любом месте приложения (после того, как, конечно же, загрузился весенний контекст).

+0

«большинство javadoc было удалено просто в качестве примера» -> lol – Fortega

ответ

8

Нет. Это не безопасный поток.

Запись переменных context не гарантируется для видимых тем, которые считывают эту переменную через getContext().

По меньшей мере, объявить context be 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(); мне это не кажется очень полезным, поскольку, как только вы его назовете, условие может измениться, и у вас нет хорошего способа получить уведомление.

+0

Это правда, но почему это имеет значение в ** этом ** сценарии?'setContext()' будет вызываться только один раз, 'getContext()' будет выходить из строя раньше (или во время), который будет ожидаться OP. – ChssPly76

+5

'getContext()' может терпеть неудачу ** "после" ** вызова 'setContext()' тоже. Без барьера памяти вы ничего не можете сказать о том, когда один поток видит действия другого потока. Другими словами, в отсутствие отношений * происходит до *, как определено в спецификации, слова типа «до», «во время» и «после» не имеют смысла. – erickson

+0

Очень хорошо, позвольте мне определить «во время», упомянутого в моем первом комментарии, как «период до момента, когда поток получателя видит изменения в« контексте ». И да, ** в теории ** это может не произойти, поскольку геттер не синхронизируется в одной и той же блокировке, а «контекст» не является изменчивым. Даже в этом случае 'getContext()' просто выдает исключение - то же самое, что и в случае, если 'setContext()' никогда не вызывался для начала. Итак, как же все это ** практически ** имеет значение в ** этом ** сценарии? – ChssPly76

1

У весны уже есть класс под названием ContextSingletonBeanFactoryLocator, который соединяет статический доступ к ApplicationContext для вас. По крайней мере, использование этого класса может сэкономить вам беспокойство, если ваш пользовательский подход будет потокобезопасным.

Однако это немного запутывает использование этого класса, так как есть немного косвенности. Вы можете посмотреть на this blog post для получения дополнительной информации о том, как работает этот вызов.

Смежные вопросы