Deadlock that you can’t avoid
A seemingly innocent user report in the Hudson IRC channel turns into an interesting “discovery” (for me anyway) about JVM. Namely, if you got two threads initializing classes in the opposite order, you can get into a dead lock.
For this test, I wrote the following class. In this way, initialization of Foo results in the initialization of Bar:
package test;
public class Foo {
static {
try {
System.out.println("Initializing Foo");
Thread.sleep(3000);
new Bar();
System.out.println("Foo initialized");
} catch (Exception e) {
throw new Error(e);
}
}
}
I then wrote the Bar class that does the opposite:
package test;
public class Bar {
static {
try {
System.out.println("Initializing Bar");
Thread.sleep(3000);
new Foo();
System.out.println("Bar initialized");
} catch (Exception e) {
throw new Error(e);
}
}
}
Now, if you initialize them simultaneously from the opposite direction like this:
public class App {
public static void main(String[] args) {
new Thread() { public void run() { new Foo(); }
}.start();
new Thread() { public void run() { new Bar(); }
}.start();
}
}
And you’ll see that it deadlocks:
"Thread-1" prio=10 tid=0x0000000040696000 nid=0x2d6e in Object.wait() [0x00007ff087ce5000] java.lang.Thread.State: RUNNABLE at test.Bar.(Bar.java:11) at test.App$2.run(App.java:14) "Thread-0" prio=10 tid=0x0000000040688000 nid=0x2d6d in Object.wait() [0x00007ff087de6000] java.lang.Thread.State: RUNNABLE at test.Foo. (Foo.java:11) at test.App$1.run(App.java:8)
Obviously, in production code, the path from initialization of class Foo to class Bar will be much longer, but you get the idea. I’m kind of surprised that this isn’t a real widespread problem in JavaEE. Developers don’t normally care about the class initialization, and on the server side you tend to have a lot of threads doing random things…
Interesting bug. Do you find that classes are often doing more-than-minor amounts of work in static initializers? Perhaps it is not seen more often because this pattern is not used that much.
Yeah, I think sometimes they do. Like the singleton pattern.
In JavaEE, most classes are loaded when deploying on an app server. The deployment is single thread. Maybe that’s why we seldom see this.
Seems like you are at risk for this if you ever have a dependency cycle between classes. Probably something to avoid in general.
Isn’t it somethign that can be avoided by using an IOC container and injecting dependencies rather than letting object A initializes its depdendency B.
This is a classic case of lock ordering deadlock discussed in “Java concurrency in practice” Eg bad code http://www.javaconcurrencyinpractice.com/listings/CooperatingDeadlock.java
Strategy suggested is to use open calls http://www.javaconcurrencyinpractice.com/listings/CooperatingNoDeadlock.java
Interesting! Apparently a known problem that was decided not to be fixed, see http://www.cs.umd.edu/~pugh/java/memoryModel/archive/1226.html
Doesn’t this really highlight the need for good coding and design? When I look at two classes with circular dependencies, that speaks of poor design to me, not necessarily a fault of the JVM.
Mao Geng: I used to work for the group that does JavaEE, and I can assure you that most classes are not loaded during the deployment. Classloading in general happens rather lazily in Java, for good reasons.
Zac Thompson: in a way yes, but in a way no. For example, if you don’t have any static initializers, you won’t get into this problem.
Phil A: Yes. But even in an IoC environment, not every instantiation happens through the container and you can definitely have some static initializers. But it might explain why we see this not as frequently as I thought.
Harsha: note that there’s no explicit synchronization involved in this code, and you can’t fix this in the same way you fix the code example you cited.
Jan Kronquist: Thanks for the pointer! It’s good to confirm that this is indeed a problem. I also find it interesting that .NET avoids this problem.
Chris A: bad coding and poor design can be blamed for everything from dangling pointers in C to slow database access in Java, so while it might be true that a “better” program can avoid this problem, I find that argument uninteresting — as you can see in the thread cited by Jan Kronquist, I think JVM could have done better in this regard, and in that sense I consider this a shortcoming of the platform.
@kohsuke
Is this an indication that you feel Java is losing its appeal for you as an Enterprise Platform, or simply that it shares imperfection with other development frameworks?
If so, without getting into a bigger discussion and losing focus, the JCP should allow the community to fix this issue should it not? I read Jan Kronquists link, but I still feel a remedy is possible if carefully thought out.
I noted the previous comment mentioning .NET’s avoidance of the threading issue.
It remembers me some trouble i had when i wrote my first lines of code some years ago. I can’t really remember what i was exactly doing, i think it was something like:
public class A {
}
@Vinicius
…
public class A {
B b = new B();
}
public class B {
A a = new A();
}
It always crashed JVM when any of these classes were instantiated. But my memory can be wrong..
@Kazuki Hamano
No, this post is just to share what I discovered. I think this kind of minor problems exist in all the platforms.
@Vinicius
You clearly got an infinite allocation loop.
As long as user developed class is loaded through single thread at start, shouldn’t be a problem.
lwpro2: That’s like saying “no dead lock will occur as long as you only ues one thread.” True, but it doesn’t help those of us who want to use multiple threads.
Classloading happens automatically in Java, making it difficult to load everything from a single class.