Как управлять выключением ExecutorService, когда мы позволяем ему внедриться?


Предположим, что я пишу службу, которая нуждается в некоторой службе исполнителя / отдельном потоке. я даю возможность использовать фабричный метод, чтобы не беспокоиться о службе исполнителя, но все же хочу разрешить прохождение существующей службы исполнителя (инъекция зависимостей).

Как я могу управлять для executorService.shutdown()?

Пример кода:

public class ThingsScheduler {

    private final ExecutorService executorService;

    public ThingsScheduler(ExecutorService executorService) {
        this.executorService = executorService;
    }

    public static ThingsScheduler createDefaultSingleThreaded() {
        return new ThingsScheduler(Executors.newSingleThreadExecutor());
    }

    public scheduleThing() {
        executorService.submit(new SomeTask());
    }

    // implement Closeable?
    // @PreDestory?
    // .shutdown() + JavaDoc?
}

Существует несколько проблем

  • мы должны иметь возможность отключить внутренне созданный исполнитель, или в лучшем случае обработать его автоматически (Spring @PreDestory, или в худшем случае finalize ())
  • нужно не отключения исполнителем, если она под внешним управлением (вводят)

Мы могли бы создать некоторый атрибут, указывающий, если executor создается нашим классом или если он вводится, а затем на finalize/@PreDestroy / shutdown hook мы могли бы закрыть его, но это не кажется мне элегантным.

Может быть, нам следует полностью отказаться от заводского метода и всегда требовать инъекции, подталкивая executor lifecycle управление для клиента?

4 2

4 ответа:

Я бы сказал, что решение полностью зависит от вас. Сторонние библиотеки, такие как spring, широко используют специальный атрибут, чтобы понять, кто должен выпустить определенный ресурс в зависимости от его создателя. mongoInstanceCreated в SimpleMongoDbFactory, localServer вSimpleHttpServerJaxWsServiceExporter и т. д. Но они делают это, потому что эти классы создаются только для внешнего использования. Если ваш класс используется только в коде приложения, то вы можете либо ввести executorService и не заботиться о его выпуске или создать и выпустить его внутри класса, который его использует. Этот выбор зависит от дизайна вашего класса/приложения (работает ли ваш класс с любым executorService, является ли executorService общим и используется другими классами и т. д.). В противном случае я не вижу другого варианта, кроме выделенного флага.

Вы можете создать экземпляр анонимного суб-внутреннего класса из вашей фабрики по умолчанию, как показано ниже. Класс определит метод close / @PreDestroy, который будет вызван вашим контейнером DI. например

public class ThingsScheduler {
    final ExecutorService executorService;

    public ThingsScheduler(ExecutorService executorService) {
        this.executorService = executorService;
    }

    /**
     * assuming you are using this method as factory method to make the returned
     * bean as managed by your DI container
     */
    public static ThingsScheduler createDefaultSingleThreaded() {
        return new ThingsScheduler(Executors.newSingleThreadExecutor()) {
            @PreDestroy
            public void close() {
                System.out.println("closing the bean");
                executorService.shutdown();
            }
        };
    }
}

Более "элегантным" решением было бы расширить свой ExecutorService и в нем переопределить метод shutdown (какой бы вы ни выбрали). В случае инъекции вы вернете этот расширенный тип, и он будет иметь свою собственную логику завершения работы. В случае с фабрикой-у вас еще есть оригинальная логика.

Поразмыслив еще немного, я пришел к следующим выводам:

  • Не думайте о том, чтобы отключить его, если он вводится-кто-то его создал, кто-то другой будет управлять его жизненным циклом
  • A executor factory может быть введен вместо Executor, тогда мы создаем экземпляр с помощью factory и управляем его закрытием самостоятельно, как мы управляем жизненным циклом (и в этом случае применяются ответы от других пользователей)