Kalle Richter
Kalle Richter

Reputation: 8728

Why does ExecutorService not process submitted tasks when started from class loader thread?

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

Answers (1)

Alexei Kaigorodov
Alexei Kaigorodov

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

Related Questions