20

I have come across a situation where my program hangs, looks like deadlock. But I tried figuring it out with jconsole and visualvm, but they didn't detect any deadlock. Sample code:

public class StaticInitializer {

private static int state = 10;

static {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            state = 11;
            System.out.println("Exit Thread");
        }
    });

    t1.start();

    try {
        t1.join();
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    System.out.println("exiting static block");
}

public static void main(String...strings) {
    System.out.println(state);
}
}

When I execute this in debug mode then I could see control reaching @Override public void run() { state = 11;

but as soon as state=11 is executed it just hangs/deadlocks. I looked in different postings in stackoverflow and I thought that static initializers are thread-safe but in that case jconsole should report this. About main thread, jconsole saying that it is in waiting state, and that's fine. But for the thread created in static initializer block, jconsole says that it is in RUNNABLE state and not blocked. I am confused and here lacking some concept. Please help me out.

4

4 回答 4

35

You're not just starting another thread - you're joining on it. That new thread has to wait for StaticInitializer to be fully initialized before it can proceed, because it's trying to set the state field... and initialization is already in progress, so it waits. However, it's going to be waiting forever, because that initialization is waiting for that new thread to terminate. Classic deadlock.

See the Java Language Specification section 12.4.2 for details about what's involved in class initialization. Importantly, the initializing thread will "own" the monitor for StaticInitializer.class, but the new thread will be waiting to acquire that monitor.

In other words, your code is a bit like this non-initializer code (exception handling elided).

final Object foo = new Object();
synchronized (foo)
{
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (foo) {
                System.out.println("In the new thread!");
            }
        });
    t1.start();
    t1.join();
});

If you can understand why that code would deadlock, it's basically the same for your code.

The moral is not to do much work in static initializers.

于 2011-09-22T16:18:39.130 回答
13

classloading is kind of a sensitive time in the jvm. when classes are being initialized, they hold an internal jvm lock which will pause any other thread trying to work with the same class. so, your spawned thread is most likely waiting for the StaticInitializer class to be fully initialized before proceeding. however, your StaticInitializer class is waiting for the thread to complete before being fully initialized. thus, deadlock.

of course, if you are truly trying to do something like this, the secondary thread is superfluous since you are joining it right after you start it (so you might as well just execute that code directly).

UPDATE:

my guess as to why the deadlock is not detected is because it is occurring at a much lower level than the level at which the standard deadlock detection code works. that code works with normal object locking, whereas this is deep jvm internal stuff.

于 2011-09-22T16:16:01.467 回答
5

I was able to get your program to run by commenting out the line state = 11;

You can not set state=11 until you have completed initialization. You can not complete initialization until t1 finishes running. T1 can not finish running until you set state=11. Deadlock.

于 2011-09-22T16:17:30.600 回答
4

Here is what I think happens:

  1. The main thread tries to initialize StaticInitializer. This involves locking the corresponding Class object.
  2. While still holding the lock, the main thread spawns another thread, and waits for it to terminate.
  3. The other thread's run() method attempts to access state, which requires StaticInitializer to be fully initialized; this involves waiting on the same lock as in step 1.

End result: a deadlock.

See the JLS for a detailed description of the intialization procedure.

If you move t1.join() into main(), everything works.

于 2011-09-22T16:23:36.967 回答