Intellij 检查工具警告在静态变量中引用子类不是一个好的做法,因为它可能会导致死锁。使用以下文字:
此检查报告的类引用了它们自己的子类 它们的静态初始值设定项或静态字段中。此类参考文献可以 在多线程环境中导致 JVM 级死锁,当一个 线程尝试加载超类,另一个线程尝试加载 同时子类化。
以下示例说明:
class Generator {
public static Generator fiveGenerator = new FixedGenerator(5);
public static Generator sixGenerator = new FixedGenerator(6);
int generateNumber() {
//some generation code
return 1;
}
private static class FixedGenerator
extends Generator {
FixedGenerator(int num) {
this.num = num;
}
@Override
int generateNumber() {
return this.num;
}
private int num;
}
}
请注意:
FixedGenerator
课程是而且应该是私人的。
fiveGenerator
和 sixGenerator
是并且应该是公开的。
对此我有两个问题:
这是一种不好的做法吗?为什么?这怎么会造成死锁呢?
等效代码实现该功能的正确方法是什么?
这段代码不会导致任何死锁。
死锁和并发有关,你没有并发。
你可能有一个初始化问题,但你也不会,因为你没有循环,因为
FixedGenerator()
调用Generator()
但Generator()
不调用FixedGenerator()
。
这是一种不好的做法吗?
这是因为类不应该引用/知道它们的子类。
等效代码实现该功能的正确方法是什么?
您可以避免私有类中的继承。
作为替代方案,您可以选择组合而不是继承。
在超类的静态初始化器中引用子类确实会在 JVM 中造成死锁。这个问题很久以前就被报告过,并被关闭为“不是错误”或“无法修复”,请参阅:
Java 1.2 和 1.4 报告了该问题,但在最新的 JVM 中仍然会出现该问题。例如,以下代码在 Java 8、11、17 和 21 中始终挂起:
public class Test {
public static void main(String[] args) {
List<Thread> threads = new ArrayList<>();
for (String classname : new String[]{"A", "B", "C", "D", "E"}) {
threads.add(new Thread(() -> {
try {
Class<?> cls = Class.forName(classname);
System.out.println(cls.getName() + " initialized");
} catch (Exception e) {
e.printStackTrace();
}
}));
}
threads.forEach(Thread::start);
}
}
class A {
static B b = new B();
static C c = new C();
static D d = new D();
static E e = new E();
}
class B extends A {}
class C extends A {}
class D extends A {}
class E extends A {}
如果没有同时访问类层次结构,JVM 将不会挂起。