views:

185

answers:

2

This code will deadlock:

public class Main {
   static public final Object a = new Object();
   static {
      Runtime.getRuntime().addShutdownHook(new Thread() {
         @Override
         public void run() { if (a == null); }
      });
      System.exit(0);
   }
   static public void main(final String[] args) {}
}

This code will exit normally:

public class Main {
   static public final Object a = new Object();
   static {
      final Object aa = a;
      Runtime.getRuntime().addShutdownHook(new Thread() {
         @Override
         public void run() { if (aa == null); }
      });
      System.exit(0);
   }
   static public void main(final String[] args) {}
}

What is happening?

A: 

Here's the bytecode for the deadlocked example:

public class Main extends java.lang.Object{
public static final java.lang.Object a;

public Main();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   return

static {};
  Code:
   0:   new     #2; //class java/lang/Object
   3:   dup
   4:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   7:   putstatic       #3; //Field a:Ljava/lang/Object;
   10:  invokestatic    #4; //Method java/lang/Runtime.getRuntime:()Ljava/lang/Runtime;
   13:  new     #5; //class Main$1
   16:  dup
   17:  invokespecial   #6; //Method Main$1."<init>":()V
   20:  invokevirtual   #7; //Method java/lang/Runtime.addShutdownHook:(Ljava/lang/Thread;)V
   23:  iconst_0
   24:  invokestatic    #8; //Method java/lang/System.exit:(I)V
   27:  return

}

And here's the bytecode for the case that finishes normally:

public class Main extends java.lang.Object{
public static final java.lang.Object a;

public Main();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   return

static {};
  Code:
   0:   new     #2; //class java/lang/Object
   3:   dup
   4:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   7:   putstatic       #3; //Field a:Ljava/lang/Object;
   10:  getstatic       #3; //Field a:Ljava/lang/Object;
   13:  astore_0
   14:  invokestatic    #4; //Method java/lang/Runtime.getRuntime:()Ljava/lang/Runtime;
   17:  new     #5; //class Main$1
   20:  dup
   21:  aload_0
   22:  invokespecial   #6; //Method Main$1."<init>":(Ljava/lang/Object;)V
   25:  invokevirtual   #7; //Method java/lang/Runtime.addShutdownHook:(Ljava/lang/Thread;)V
   28:  iconst_0
   29:  invokestatic    #8; //Method java/lang/System.exit:(I)V
   32:  return

}

The bytecode is obviously different. I'll either come up with the answer or someone else who understands the internals of the JVM will help.

duffymo
+3  A: 

It is important that classes are not accessed concurrently whilst initialising, so a lock is held.

I guess what is happening in the first case is:

  • The main thread holds the initialisation lock for Main.
  • Whilst holding the lock, System.exit blocks as it does not return.
  • The shutdown hook executes.
  • The shutdown tries to access the Main class to read a field, but blocks as the class is initialising.

Hence the deadlock. It's a little clearer if you write if (a == null); as if (Main.a == null);.

In the second case, the value is copied and therefore the shutdown hook does not need to access the Main class.

Moral: Don't mix threads and class initialisation. Gafter and Bloch's Java Puzzlers book has more on this.

Tom Hawtin - tackline
Since the static variable is final, it seems silly it needs to access the class at all. Thanks for the Main.a reference.
Andrew Raffensperger
@Anderw If it was a compile-time constant, then it would be copied into the class file. As it is, the reference to the particular object needs to be copied at runtime. Generally with Java it operates in the simplest way possible (would you expect behaviour to change if it wasn't final, or if it came after the static block?). (The Java Memory Model (JMM) does permit some assumptions about final instance fields. Not sure about final static fields.)
Tom Hawtin - tackline