Reputation: 8728
I have a class Singleton
(simplified for this example)
public class Singleton {
private final static Lock METHOD_1_LOCK = new ReentrantLock();
private final static Lock METHOD_2_LOCK = new ReentrantLock();
static {
try {
init();
}catch(InterruptedException ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static void init() throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(() -> {
method1();
});
executorService.submit(() -> {
method2();
});
executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
public static List<String> method1() {
METHOD_1_LOCK.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
return Stream.of("b").collect(Collectors.toList());
}finally {
METHOD_1_LOCK.unlock();
}
}
public static List<String> method2() {
METHOD_2_LOCK.lock();
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2");
return Stream.of("a").collect(Collectors.toList());
}finally {
METHOD_2_LOCK.unlock();
}
}
private Singleton() {
}
}
which I wanted to pre-initialize by calling Class.forName
on a separate thread:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Class.forName(Singleton.class.getName());
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
// working alternative:
// try {
// Singleton.init();
// }catch(InterruptedException ex) {
// ex.printStackTrace();
// }
});
thread.start();
thread.join();
}
This construct never returns (is expected to return after 1 seconds and a bit) from ExecutorService.awaitTermination
.
If I switch to the code marked "working alternative" by commenting it in (and commenting out the other) and comment out the static
block in Singleton
the code is executed as expected (method1
and method2
are invoked and return in the opposite order according to output).
Since the "working alternative" prevents the problem and I can live with it I'm looking for an explanation for this behavior.
My intention to use Class.forName
rather than Singleton.init
was to be able to add more static initialization tasks to Singleton
without the need to think whether they're covered by the pre-initialization routine. I agree that this whole setup is not ideal.
Upvotes: 0
Views: 235
Reputation: 13525
You are moving in wrong direction. First, never do heavy computations while class initialization. Until class initialization finishes, invocation of the class methods is restricted. The idea is not to show not yet initialized variables to the class methods. Only methods called directly from the static initializer can be executed, otherwise they are blocked. In your case, invocations of method1
and method2
from parallel tasks are blocked.
Generally, avoid static variables whenever possible. Create instances of objects instead. For the given case, create an instance of class Singleton, with all the variables converted from static to instance fields.
Lastly, do not run a thread only to call
thread.start();
thread.join();
better directly call the method passed into the thread as Runnable
.
Upvotes: 3