Почему ExecutorService не обрабатывает отправленные задачи при запуске из потока загрузчика классов?

У меня есть класс Singleton (упрощенный для этого примера)

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() {
    }
}

который я хотел предварительно инициализировать, вызвав Class.forName в отдельном потоке:

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();
}

Эта конструкция никогда не возвращается (ожидается, что она вернется через 1 секунду и немного) из ExecutorService.awaitTermination.

Если я переключусь на код с пометкой «рабочая альтернатива», закомментировав его (и закомментировав другой) и закомментировав блок static в Singleton, код будет выполнен, как и ожидалось (method1 и method2 вызываются и возвращаются в противоположном порядке в соответствии с выход).

Поскольку «рабочая альтернатива» предотвращает проблему, и я могу с ней жить, я ищу объяснение такому поведению.

Я намеревался использовать Class.forName, а не Singleton.init, чтобы иметь возможность добавлять больше статических задач инициализации в Singleton, не задумываясь о том, покрываются ли они подпрограммой предварительной инициализации. Я согласен, что вся эта установка не идеальна.


person Karl Richter    schedule 06.01.2019    source источник


Ответы (1)


Вы движетесь в неправильном направлении. Во-первых, никогда не выполняйте тяжелые вычисления во время инициализации класса. Пока не завершится инициализация класса, вызов методов класса ограничен. Идея не в том, чтобы показывать еще не инициализированные переменные методам класса. Могут выполняться только методы, вызываемые непосредственно из статического инициализатора, в противном случае они блокируются. В вашем случае вызовы method1 и method2 из параллельных задач заблокированы.

Как правило, по возможности избегайте статических переменных. Вместо этого создавайте экземпляры объектов. Для данного случая создайте экземпляр класса Singleton со всеми переменными, преобразованными из статических в поля экземпляра.

Наконец, не запускайте поток только для вызова

  thread.start();
  thread.join();

лучше напрямую вызвать метод, переданный в поток, как Runnable.

person Alexei Kaigorodov    schedule 06.01.2019
comment
Это немного значит, что JRE просто не выдает исключение, а вместо этого блокируется навсегда. - person Karl Richter; 06.01.2019
comment
блокирует навсегда из-за взаимоблокировки. Тупик не легко обнаружить. - person Alexei Kaigorodov; 06.01.2019