Этот код зайдет в тупик:
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) {}
}
Этот код будет обычно выходить:
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) {}
}
Что происходит?
Важно, чтобы классы не использовались одновременно во время инициализации, поэтому блокировка сохраняется.
Я предполагаю, что в первом случае происходит следующее:
Main
. System.exit
блокируется, поскольку не возвращается. Main
для чтения поля, но блокируется по мере инициализации класса. Отсюда и тупик. Будет немного понятнее, если вы напишете if (a == null);
как if (Main.a == null);
.
Во втором случае значение копируется, и поэтому ловушка выключения не нуждается в доступе к классу Main
.
Мораль: не смешивайте потоки и инициализацию класса. В книге Гафтера и Блоха «Java-головоломки» об этом есть больше.
Вот байт-код для примера взаимоблокировки:
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
}
А вот байт-код для случая, который заканчивается нормально:
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
}
Байт-код явно отличается. Я либо найду ответ, либо мне поможет тот, кто разбирается во внутреннем устройстве JVM.