Intellij inspection tool warns about referencing a subclass in a static variable isn't a good practice because it can cause a deadlock. The following text is used:
This inspection reports classes that refer to their own subclasses in their static initializers or in static fields. Such references can cause JVM-level deadlocks in multithreaded environment, when one thread tries to load superclass and another thread tries to load subclass at the same time.
The following examples demonstrates that:
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;
}
}
Note that:
FixedGenerator class is and should be private.
fiveGenerator and sixGenerator are and should be public.
On that I have two questions:
Is it a bad practice? Why? How can a deadlock be caused by this?
What's the right way to equivalent code implement that?
This code will not cause any deadlock.
Deadlock is related to concurrency and you don't have concurrency.
You could have an initialization issue but you will not either as you don't have cycle as FixedGenerator() invokes Generator() but Generator() doesn't invoke FixedGenerator().
Is it a bad practice?
It is as classes should not reference/know their subclasses.
What's the right way to equivalent code implement that?
You could avoid inheritancy in the private class.
As alternative, you could favor the composition over the inheritancy.
Referencing a subclass in the static initializer of super class can indeed create a deadlock in the JVM. This issue was reported a long time ago and closed as "not a bug" or "won't fix", see:
The issue was reported for Java 1.2 and 1.4, but it still occurs with recent JVMs. For example the following code consistently hangs with Java 8, 11, 17 and 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 {}
If the class hierarchy isn't accessed concurrently the JVM will not hang.
Also worth noting, if the super type is an interface and not an abstract class, the JVM doesn't hang. In the example above, turning A into an interface fixes the issue. And in the original question, if the Generator class becomes an interface, and its generateNumber() method is modified with the default keyword, the code will be safe.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With