21

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.

thomas
  • 310
  • 2
  • 12
  • Good question why the thread is RUNNABLE during join(). It is clearly WAITING, so that would be a more proper state. – irreputable Sep 22 '11 at 18:10
  • actually, the javadoc says it should be in WAITING state: http://download.oracle.com/javase/6/docs/api/java/lang/Thread.State.html#WAITING – irreputable Sep 22 '11 at 18:11
  • That is funny. I came here because I did some programming with scala worksheets, https://mail.google.com/mail/u/0/#inbox/1472a9cccdc4ffbd This is really cool paradigm that allows the results of computation appear online, as you type. It implies however that all the work is done in the static initializer. Can you condemn that? – Val Jul 12 '14 at 14:34

4 Answers4

36

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    Hi Jon first of all thanks. But I would like to know what would be the state of the thread which is spawned ? And why jconsole is not detecting a deadlock ? – thomas Sep 22 '11 at 16:27
  • A stronger moral is not to use Threads if you don't have to. No Concurrent Programming -> No Deadlock. – emory Sep 22 '11 at 16:29
  • @thomas: The new thread would be waiting to acquire the lock on StaticInitializer.class (BLOCKED). The original thread would be waiting to join on the new thread (WAITING). I don't know why jconsole doesn't detect it. – Jon Skeet Sep 22 '11 at 16:32
  • 3
    @emory: Well, I'm not sure - in my experience there are more cases where it's worth using multiple threads than there are cases where it's worth doing significant work in class initializers. – Jon Skeet Sep 22 '11 at 16:33
  • @thomas - the deadlock is probably not detected because it is occurring at a much lower level than the standard deadlock detection code works. that code works with normal object locking, whereas this is deep jvm internal stuff. – jtahlborn Sep 22 '11 at 16:34
  • @JonSkeet: My analysis was same to your explanation. But too much faith over thread-dump and jconsole confused me :(. – thomas Sep 22 '11 at 16:41
  • @thomas, i will comment here, since it's a decent answer (So Jon, excuse if it's just pollution). While JVM compiles the inner Runnable and it touches the StaticInitializer class (via PUTFIELD instruction - `state=11`), it has to wait to complete the class initialization (``) OR to initialize it itself. Since it cannot do any. It is stuck. – bestsss Sep 25 '11 at 12:27
  • 2
    @Jon, *I don't know why jconsole doesn't detect it.* jstack/jconsole can see objects that are waiting upon (or synchronized) or being parked via `java.util.concurrent.locks.LockSupport.park(Object)`. The class initialization doesn't use any of those. – bestsss Sep 25 '11 at 20:06
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.

jtahlborn
  • 52,909
  • 5
  • 76
  • 118
  • It is a good point about t1 being superfluous but you can extend it. why not just initialize state to 11. With t1 being superfluous there is no need to print "Exit Thread". The whole thing can be accomplished with "private static int state = 11;" – emory Sep 22 '11 at 16:26
  • 2
    @emory - sure, i agree. i assume the OP was simplifying some more interesting scenario. – jtahlborn Sep 22 '11 at 16:29
  • thanks jtahlborn. I agree whatever you have reasoned. But regarding spawned thread: why its state is not waiting or blcked but runnable ? – thomas Sep 22 '11 at 16:32
  • @thomas - commented on that in your question on the other post. – jtahlborn Sep 22 '11 at 16:36
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.

emory
  • 10,725
  • 2
  • 30
  • 58
  • thanks emory. I guessed as well that this is deadlock. But what should I expect in thread-dump and jconsole thread state about the state of the spawned thread ? – thomas Sep 22 '11 at 16:30
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.

NPE
  • 486,780
  • 108
  • 951
  • 1,012