Как завершить работу и переустановить службу исполнителя без потери задач

#java #executorservice

#java #executorservice

Вопрос:

Я не совсем понимаю, как я должен реализовать ExecutorService в приложении с периодически возникающими задачами. Каждая задача выполняется около 15 минут (в разы дольше). Иногда в день выполняется 10 задач, а иногда их нет целую неделю.

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

Теперь моя проблема в том, что я должен убедиться, что все задачи выполнены. Это означает, что после вызова shutdown я должен убедиться, что создан экземпляр новой службы исполнителя, прежде чем отправлять новую задачу. Мне нужно подождать, пока старая служба исполнителя не будет закрыта (или прекращена?) и инициализируйте новую. Как я должен это реализовать?

Это мой подход:

 // sync block to check for each task if a new Executor Service has to be created before submitting the task
synchronized (this) {
    // do I have to check both isSutdown and isTerminated?
    if (null == businessTaskExecutor || businessTaskExecutor.isShutdown() || businessTaskExecutor.isTerminated()) {
        // simply init a new one
        businessTaskExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
    } else if (businessTaskExecutor.isTerminating()) {
        // the termination process has started and is not done.
        // Do I have to wait for the old executor or can I simply init a new one without waiting?
        while (!businessTaskExecutor.isTerminated()) { }
        businessTaskExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
    }
}
Future<?> future = businessTaskExecutor.submit(() -> new BusinessTaskRunnable(businessTask));
if (future.isDone() amp;amp; businessTaskExecutor.getQueue().isEmpty()) {
    businessTaskExecutor.shutdown();
    try {
        if (!businessTaskExecutor.awaitTermination(1, TimeUnit.HOURS)) {
            businessTaskExecutor.shutdownNow();
        }
    } catch (InterruptedException ex) {
        businessTaskExecutor.shutdownNow();
        Thread.currentThread().interrupt();
    }
}
 

Объяснение:

Если исполнитель имеет значение null / shutdown / terminated, я инициализирую новый, пока все хорошо. В противном случае, если исполнитель в настоящее время завершает работу (поскольку было вызвано завершение работы), никакие дополнительные задачи не будут приняты исполнителем. Поэтому я жду, пока она не будет завершена, а затем инициализирую новую. После этого я отправляю свою задачу… Я завершаю работу исполнителя только в том случае, если в очереди больше нет задач. Несмотря на то, что я жду час, поскольку все еще может быть запущенная задача.

Есть ли лучшая реализация желаемого поведения?

Дополнительная информация: я использую ThreadPoolExecutor, поскольку мне нужен getQueue(), чтобы проверить, пусто ли оно. Я мог бы также использовать любой другой вид службы исполнителя в разных реализациях.

Заранее спасибо.

Комментарии:

1. Почему бы просто не оставить исполнителя рядом? Повторная инициализация все время просто бессмысленна. Вы можете сохранить основные потоки равными нулю, если чувствуете, что они занимают слишком много ресурсов (вам действительно не следует выполнять кастинг ThreadPoolExecutor , этот код намного сложнее, чем должен быть).

2. Я читал, что сборщик мусора будет вызван только в том случае, если я выключу исполнителя. Каждая задача создает много мусора, который приведет к OOME. Есть ли еще одна война для вызова gc без выключения службы исполнителя?

3. Простой ExecutorService не имеет метода getQueue(), поэтому я передаю его в ThreadPoolExecutor. есть ли лучший способ инициализации ThreadPoolExecutor? Или есть способ получить доступ к очереди с помощью ExecutorService?

4. Сборщик мусора не зависит от какой-либо службы исполнителя. Если вы правильно выполнили задачи, как только они будут завершены, они должны быть очищены GC, поскольку ничто другое не должно содержать ссылку. Если у вас проблемы с памятью, вам следует разобраться в основной причине, а не искать обходные пути.

5. Вы можете создать a new ThreadPoolExecutor() , который позволяет настраивать его несколькими способами. Ваше предположение о «помощи GC» совершенно неверно, исполнитель не предотвращает выполнение задач из GCd (если вы не делаете что-то крайне неправильное). Не завершайте работу исполнителя, если вы просто собираетесь воссоздать его снова.

Ответ №1:

Я хочу завершить работу исполнителя, поскольку он больше не нужен, и чтобы сборщик мусора мог выполнять свою работу.

Но это необходимо, поэтому вы будете создавать его заново. GC может выполнять свою работу независимо от того, отключен исполнитель или нет (если вам не удалось создать что-то особенное, например ThreadLocal , с помощью чего можно было бы каким-то образом привязать объекты к исполнителю).

Я читал, что сборщик мусора будет вызван только в том случае, если я выключу исполнителя.

Вы либо неправильно помните, либо читали неверную информацию.

Поскольку вы работаете с одним потоком, вам даже не нужно беспокоиться о конфигурации пула. Все, что вам нужно, — это один долгоживущий Executors.newFixedThreadPool(1); .

В случае, если вам действительно нужно освободить ресурсы, вы можете настроить <a rel=»noreferrer noopener nofollow» href=»https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html#(int,int,long,java.util.concurrent.TimeUnit,java.util.concurrent.BlockingQueue)» rel=»nofollow noreferrer»>размер основного пула с помощью чего-то вроде new ThreadPoolExecutor(0, 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<BusinessTaskRunnable>()); . Но это не имеет значения для микрооптимизации, один поток не должен иметь значения.