Java 以下实用程序类是线程安全的吗?
首先让我们看一下实用程序类(大多数javadoc已被删除,仅用于示例): Spring将在应用程序启动后调用setApplicationContext方法一次。假设Nincompop以前没有调用ApplicationContextUtils.setContext(),它应该锁定对实用程序类中上下文的引用,从而允许对getContext()的调用成功(意味着isInitialized()返回true) 我只想知道这个类是否违反了良好编码实践的任何原则,特别是在线程安全方面(但其他发现的愚蠢之处是受欢迎的) 谢谢你帮助我成为一名更好的程序员,StackOverflow 问候,, 莱斯Java 以下实用程序类是线程安全的吗?,java,multithreading,spring,static,thread-safety,Java,Multithreading,Spring,Static,Thread Safety,首先让我们看一下实用程序类(大多数javadoc已被删除,仅用于示例): Spring将在应用程序启动后调用setApplicationContext方法一次。假设Nincompop以前没有调用ApplicationContextUtils.setContext(),它应该锁定对实用程序类中上下文的引用,从而允许对getContext()的调用成功(意味着isInitialized()返回true) 我只想知道这个类是否违反了良好编码实践的任何原则,特别是在线程安全方面(但其他发现的愚蠢之处是受
另外,我没有说明为什么我需要这个实用程序类——只要我确实有合法的需要从应用程序中任何地方的静态上下文访问它就足够了(当然,在spring上下文加载之后)。不。它不是线程安全的 通过
getContext()
读取类变量的线程不能保证对该类变量的写入可见
至少,将上下文
声明为易失性
。理想情况下,通过如下调用将上下文
重新定义为原子引用
:
if(!context.compareAndSet(null, theContext))
throw new IllegalStateException("The context is already set.");
下面是一个更完整的示例:
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
}
}
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();
}
}
公共类应用程序上下文{
私有静态最终原子引用上下文=
新原子引用();
公共静态应用程序上下文getContext(){
ApplicationContext ctx=context.get();
如果(ctx==null)
抛出新的非法状态异常();
返回ctx;
}
公共静态布尔值已初始化(){
返回上下文。get()==null;
}
静态void setContext(最终应用程序上下文ctx){
如果(ctx==null)
抛出新的IllegalArgumentException();
if(!context.compareAndSet(null,ctx))
抛出新的非法状态异常();
}
公共静态T getBean(最终字符串名、最终类类型){
if(type==null)
抛出新的IllegalArgumentException();
返回type.cast(getContext().getBean(名称,类型));
}
私有应用程序上下文(){
抛出新的断言错误();
}
}
请注意,除了线程安全性之外,这还提供了类型安全性,利用了传递到getBean()
方法中的Class
实例
我不确定您打算如何使用
isInitialized()
方法;它对我来说似乎不是很有用,因为只要你调用它,情况就会发生变化,而且你没有一个很好的方式得到通知。Spring已经有一个名为Wired的类,可以为你连接对应用上下文的静态访问。至少,使用这个类可以省去您担心自定义方法是否是线程安全的麻烦
然而,一开始使用这个类有点混乱,因为有一点间接性。您可以查看有关此调用如何工作的更多信息。没错,但为什么在这种情况下它很重要setContext()
将只被调用一次,getContext()
将在OP预期的调用之前(或期间)失败。getContext()
调用setContext()
之后也会失败。如果没有内存障碍,您就无法说明一个线程何时看到另一个线程的操作。换句话说,在没有规范中定义的before-before关系的情况下,像“before”、“during”和“after”这样的词是没有意义的。很好,让我将第一条评论中提到的“during”定义为“getter线程看到上下文的更改之前的时间”。是的,理论上这可能永远不会发生,因为getter不是在同一个锁上同步的,context
不是易变的。即使在这种情况下,getContext()
也会简单地抛出一个异常,这与从未调用setContext()
时的情况相同。那么,在这个场景中,这些实际上又有什么关系呢?如果一个线程正在循环,比如说:while(!ApplicationContextUtils.isInitialized()){}Service myService=ApplicationContextUtils.getBean(“Service”,Service.class);JVM可以将其优化为:while(true){…无限循环…}?@LES2:正确,完全正确。如果JVM愿意,它完全有权进行这样的观察。这种情况可能永远不会发生,但当您从客户机切换到服务器VM,或从热点切换到JRockit,或您的方法被调用1000001次,从而触发某种JIT重新编译,或诸如此类时,这种情况可能会停止工作。大多数javadoc都被删除了,只不过是简单的例子“->lol
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();
}
}