Почему ExecutorService не обрабатывает отправленные задачи при запуске из потока загрузчика классов? - PullRequest
0 голосов
/ 06 января 2019

У меня есть класс 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 без необходимости думать, охватываются ли они подпрограммой предварительной инициализации. Я согласен, что вся эта установка не идеальна.

1 Ответ

0 голосов
/ 06 января 2019

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

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

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

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

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

...