nanotvi nanotvi - 3 months ago 14
Java Question

Accessing a static method when initializing a static field of same class from another thread

I had a very peculiar problem happening to me that I could not solved except splitting up the Problem into two classes.

I would like to know if there is maybe a solution without splitting the class and I would more importantly like to know if anybody has an idea why the Java Engine is deciding to act the way it does.

Edit: as shown by Jaims below, the threads don't even start at all.

The Problem:
I have a class with a static method, a static field and a constructor. The static field is initialized to an instance of the class itself. During the instance initialization I want to access the aformentioned static method. See the following code:

public class Simple {
public Simple() {
int count = 4;

for (int i = 0; i < count; i++) {
System.out.println("Simple: " + Simple.isFlag());
}

}

private static Simple i = new Simple();

public static boolean isFlag() {
return true;
}

public static void run() {

}
}

public class Main {

public static void main(String[] args) {
Simple.run();
}

}


This code runs absolutely fine. The output can be seen below:

Simple: true
Simple: true
Simple: true
Simple: true


The output is generated after I call the
run()
method because the stativ field i is only initialized after I access the first static member of that class.

I now want to do the exact same thing except with multiple threads. See here:

public class Parallel {
public Parallel() {
int count = 4;

CountDownLatch latch = new CountDownLatch(4);
for (int i = 0; i < count; i++) {
Thread t = new Thread(() -> {
System.out.println("Parallel: " + Parallel.isFlag());
latch.countDown();

Thread.currentThread().interrupt();
});

t.start();
}

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

}

private static Parallel i = new Parallel();

public static boolean isFlag() {
return true;
}

public static void run() {

}
}

public class Main {

public static void main(String[] args) {
Parallel.run();
}

}


This returns nothing. The main thread is stuck at
latch.await();
, while the other threads are stuck at
Parallel.isFlag()
.

This does not make any sense to me. Why is this not working, but the first case is? Essentially they are doing the same.

I would like to know how the Java Engine decides on when to wait and when not. Can this be changed somewhere in code?

Additionally, this has nothing to do with CountDownLatch but solely with the multithreading. Look at this final sample:

public class NonParallel {
public NonParallel() {
int count = 4;

CountDownLatch latch = new CountDownLatch(4);
for (int i = 0; i < count; i++) {
System.out.println("NonParallel: " + NonParallel.isFlag());
latch.countDown();
}

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

private static NonParallel i = new NonParallel();

public static boolean isFlag() {
return true;
}

public static void run() {

}
}

public class Main {

public static void main(String[] args) {
NonParallel.run();
}

}


This works fine. The output is as following:

NonParallel: true
NonParallel: true
NonParallel: true
NonParallel: true





Edit: none of this applies when the object initlization is not part of the class initilization. This is purely about class initialization which only happens when using a static object as described in this question. See here:

public class NonStaticParallel {
public NonStaticParallel() {
int count = 4;

CountDownLatch latch = new CountDownLatch(4);
for (int i = 0; i < count; i++) {
Thread t = new Thread(() -> {
System.out.println("NonStaticParallel: " + isFlag());
latch.countDown();

});

t.start();
}

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

}


public static boolean isFlag() {
return true;
}

public static void run() {
new NonStaticParallel();
}

}


This one works without any issue:

Parallel: true
Parallel: true
Parallel: true
Parallel: true





Answers:

Andreas provides an explanation as to what is going on.

Jaims is right in that the threads do not even start at all. This probably happens because they need the class to be initialized and they are immediately therefore blocked.

Yoshi provides a link and an excerpt from the the spec, and is therefore marked as the right answer, as this is what I wanted.

Answer

I tried your code and did two things:

  1. First, I made the lambda a static inner class of Parallel ... just in case; this didn't change anything.
  2. Since you commented that the threads are stuck on Parallel.isFlag() I tried replacing the call with just true... and it worked!

So, I did a little research and I found this, which sounds like a promising explanation for what is going on: http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2

Specifically this part:

For each class or interface C, there is a unique initialization lock LC. The mapping from C to LC is left to the discretion of the Java Virtual Machine implementation. The procedure for initializing C is then as follows:

  1. Synchronize on the initialization lock, LC, for C. This involves waiting until the current thread can acquire LC.

  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 step.

(Emphasis added.) So this would suggest the following:

  1. Main thread started class initialization while evaluating private static Parallel i = new Parallel(); and started up the threads. Then it waited on latch.await(). Class object for Parallel should indicate that initialization is "in progress."
  2. Started threads also try to reference a static member of Parallel. Each thread sees that initialization is in progress and decides to wait until the Main thread (which is now waiting on the threads to count down the latch) is done. Clearly this is a deadlock.
Comments