在IBM JVM下,当多个线程试图在不同对象上同时调用Class.getAnnotation(但具有相同的注释)时,我们遇到了一个问题。线程开始在Hashtable内的监视器上等待死锁,Hashtable用作IBM JVM中注释的缓存。最奇怪的是,持有此监视器的线程在Hashtable.get内置于“等待条件”状态,使所有其他线程无限期地等待。
IBM的支持表示,Class.getAnnotation的实现不是线程安全的。
与其他JVM实现(例如,OpenJDK)相比,我们看到它们以线程安全的方式实现了Class方法。 IBM JVM是一个封闭的源JVM,它们确实将一些源代码与它们的JVM一起发布,但是只要它们的Class实现是线程安全的,它就不足以做出明确的判断。
只要其方法是线程安全的,Class文档就不会明确说明。因此,将类方法(特别是getAnnotation)视为线程安全或者在多线程环境中必须使用同步块是一种安全的假设吗?
流行框架(例如Hibernate)如何缓解这个问题?我们在使用getAnnotation方法的Hibernate代码中没有发现任何同步用法。
答案 0 :(得分:6)
您的问题可能与Oracle Java版本8中修复的错误有关。
一个线程在带注释的类上调用isAnnotationPresent 尚未为其定义的类加载器初始化注释。这个 将导致对AnnotationType.getInstance的调用,锁定该类 sun.reflect.annotation.AnnotationType的对象。 getInstance会 导致该注释的Class.initAnnotationsIfNecessary, 试图获取该注释的类对象的锁定。
同时,另一个线程已请求Class.getAnnotations 对于那个注释(!)。由于getAnnotations锁定了它的类对象 被请求时,第一个线程在遇到它时无法锁定它 Class.initAnnotationsIfNecessary用于该注释。但线程 持有锁将尝试获取类对象的锁 AnnotationType.getInstance中的sun.reflect.annotation.AnnotationType 由第一个线程保持,从而导致死锁。
JDK-7122142 : (ann) Race condition between isAnnotationPresent and getAnnotations
答案 1 :(得分:4)
嗯,没有指定的行为,所以通常处理它的正确方法是说“如果没有指定行为,则假定没有安全保证”。
但是...
这里的问题是,如果这些方法不是线程安全的,那么规范缺少如何正确实现线程安全的文档。回想一下java.lang.Class
的实例在整个应用程序的所有线程中都是可见的,如果你的JVM托管多个apps / applets / servlets / beans /等,甚至在多个应用程序中也是如此。
因此,与您自己实例化的类不同,您可以控制对这些实例的访问,您不能阻止其他线程访问特定java.lang.Class
实例的相同方法。因此,即使我们参与依赖于某种约定的非常尴尬的概念来访问这样的全局资源(例如说“调用者必须做{{1} “),这里的问题是,甚至更大,没有这样的约定存在(嗯,或者没有记录到相同的那些)。
因此,在这种特殊情况下,没有记录调用者的责任,如果没有这样的文档就无法建立,IBM负责告诉他们如何思考,程序员在非实现时应该正确使用这些方法线程安全的方式。
我想补充一种替代解释:所有信息,synchronized(x.class)
提供,都是静态不变的。这个类反映了总是被编译到课堂中的内容。它没有改变任何状态的方法。所以也许没有额外的线程安全文档,因为所有信息都被认为是不可变的,因此自然是线程安全的。
相反,根据需要加载一些信息的事实是程序员不需要知道的未记录的实现细节。因此,如果JRE开发人员决定实现延迟创建以提高效率,那么他们必须维护类似不可变行为,阅读线程安全。