3

Here is some simple code:

class B {final static int x = C.x;}
class C {final static int x = B.x;}
class A {
    static {
        System.out.println("A static{}");
        new Thread() { public void run() { new B(); }}.start();
        new Thread() { public void run() { new C(); }}.start();
    }
    public static void main(String[] args) {
        System.out.println("A main");
        System.out.println("B.x: " + B.x);
        System.out.println("C.x: " + C.x);
    }
}

B.x and C.x are defined in terms of each other. I thought this should not compile but it does.

It freezes in main when I try to run it:

$ javac *.java && java A
A static{}
A main

Why?

Yet, it works fine in gcj:

$ gcj --main=A -o a *.java && ./a
A static{}
A main
B.x: 0
C.x: 0

Why?

Also, if I get rid of the threads,

class B {final static int x = C.x;}
class C {final static int x = B.x;}
class A {
    static {
        System.out.println("A static{}");
        new B(); 
        new C(); 
    }
    public static void main(String[] args) {
        System.out.println("A main");
        System.out.println("B.x: " + B.x);
        System.out.println("C.x: " + C.x);
    }
}

It works fine in both java and gcj:

$ javac *.java && java A
A static{}
A main
B.x: 0
C.x: 0
$ gcj --main=A -o a *.java && ./a
A static{}
A main
B.x: 0
C.x: 0

And all the variables are set to 0. Why? Shouldn't this fail to compile since the variables are static final and are never assigned anywhere?

Dog
  • 7,707
  • 8
  • 40
  • 74

1 Answers1

2

You are creating a deadlock condition which may or may not be a problem depending on how fast you start your threads.

When you use a class for the first time, it initialises the class and calls the static blocks. It does this in a thread safe manner and no other thread can access the class until that is done.

You have two threads and in the deadlock case, one has class B and wants class C, the other has class C and want class B.

As this is pretty quick, one thread can run to completion before the other start and in this case, no deadlock occurs.

Shouldn't this fail to compile since the variables are static final and are never assigned anywhere?

You have assigned the value, however you access the value while it is being initialised which is why you see the default value of 0.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Isn't the whole point in the lock around classes to prevent the class from being used before it's initialized? Then why can the default variables of these final fields be seen when there is only one thread? – Dog Aug 02 '13 at 13:19
  • I think this answer is incorrect. Class initialization is defined in a way that makes it deadlock-free; see section 5.5 here: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html – Tom Tromey Aug 03 '13 at 20:12
  • @TomTromey Here are examples http://stackoverflow.com/questions/7517964/program-hangs-if-thread-is-created-in-static-initializer-block http://stackoverflow.com/questions/6686333/deadlock-caused-by-thread-join-in-a-static-block http://stackoverflow.com/questions/8610369/deadlock-caused-while-creating-a-thread-in-a-static-block-in-java http://stackoverflow.com/questions/17236561/why-does-this-code-produce-deadlock – Peter Lawrey Aug 05 '13 at 07:02
  • Those examples all use Thread.join, which introduces the deadlock possibility. I think if you walk through the algorithm in JVMS 5.5 with the above program in mind, you'll see there is no reason it should deadlock. – Tom Tromey Aug 05 '13 at 13:50
  • I don't see why it couldn't. There is nothing which states that a thread which holds the lock for a class would release it before it has completed initialisation. In this case each thread can hold the lock for one and class while needing another lock for the other class. – Peter Lawrey Aug 05 '13 at 14:42
  • 1
    No, that isn't true. By step 6 in the procedure outlined in section 5.5, the lock ("LC") is always released. It is basically only held when modifying the state of the class being initialized. The mutual initialization case is covered by this algorithm and the program in this question presents no problems for it. – Tom Tromey Aug 05 '13 at 19:29
  • @TomTromey There is a lock LC on there status of the class. Step 2 "If the Class object for C indicates that initialization is in progress for C by some other thread, then *release LC* and *block the current thread until informed* that the in-progress initialization has completed, at which time repeat this procedure." This means the thread which isn't initialising the class blocks until told it has been initialised. In the case above the there is a thread which is initialising B and another initialising C, both blocking until it is told the other class has finished initialising. – Peter Lawrey Aug 06 '13 at 08:03
  • Yeah, I was thinking about it last night, and I finally realized that you were right and I was wrong. Sorry about that. I'll delete my (non-) answer. – Tom Tromey Aug 06 '13 at 13:18
  • @TomTromey I can see why this thought what you did. Normally a thread only blocks while holding a lock, but in this case it releases the lock before blocking. – Peter Lawrey Aug 06 '13 at 13:20