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…

22 Comments Add yours

  1. Matt B says:

    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.

  2. kohsuke says:

    Yeah, I think sometimes they do. Like the singleton pattern.

  3. Mao Geng says:

    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.

  4. Zac Thompson says:

    Seems like you are at risk for this if you ever have a dependency cycle between classes. Probably something to avoid in general.

  5. Phil A says:

    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.

  6. Harsha says:

    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

  7. Interesting! Apparently a known problem that was decided not to be fixed, see http://www.cs.umd.edu/~pugh/java/memoryModel/archive/1226.html

  8. Chris A says:

    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.

  9. kohsuke says:

    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.

  10. kohsuke says:

    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.

  11. kohsuke says:

    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.

  12. kohsuke says:

    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.

  13. kohsuke says:

    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.

  14. kohsuke says:

    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.

  15. @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.

  16. Vinicius says:

    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 {

    }

  17. Vinicius says:

    @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.. 🙂

  18. kohsuke says:

    @Kazuki Hamano

    No, this post is just to share what I discovered. I think this kind of minor problems exist in all the platforms.

  19. kohsuke says:

    @Vinicius

    You clearly got an infinite allocation loop.

  20. lwpro2 says:

    kohsuke :
    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.

    As long as user developed class is loaded through single thread at start, shouldn’t be a problem.

  21. kohsuke says:

    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.

Leave a Reply

Your email address will not be published. Required fields are marked *