Превращение ExecutorService в демон в Java

Я использую ExecutoreService в Java 1.6, просто запущенный

ExecutorService pool = Executors.newFixedThreadPool(THREADS). 

Когда мой основной поток завершится (вместе со всеми задачами, обрабатываемыми пулом потоков), этот пул предотвратит завершение моей программы до тех пор, пока я не вызову явным образом

pool.shutdown();

Могу ли я избежать этого, каким-то образом превратив управление внутренним потоком, используемым этим пулом, в поток демона? Или мне что-то здесь не хватает.


person Antiz    schedule 14.12.2012    source источник
comment
Я, как автор принятого в настоящее время ответа, предлагаю прочитать подход, опубликованный Marco13: stackoverflow.com/a/29453160/1393766 и изменить отметка о принятии из моего сообщения на его, поскольку описанное там решение, вероятно, является самым простым и приближается к тому, чего вы изначально хотели достичь.   -  person Pshemo    schedule 05.04.2015
comment
@Pshemo Позвольте мне не согласиться с вами ... Пожалуйста, проверьте мой комментарий под ответом Marco13.   -  person Vlad    schedule 07.04.2015
comment
@Pshemo Учитывая намек Владимира, вы можете подумать о том, чтобы указать на тот факт, что простое решение не всегда может быть предпочтительным.   -  person Marco13    schedule 08.04.2015
comment
@ Marco13 Я поставил, вероятно, в начале своего ответа именно по этой причине. Но может быть хорошей идеей добавить в свой ответ дополнительную информацию об идее, лежащей в основе этого, и, возможно, некоторые общие советы, например, какое может быть разумное значение keepAliveTime в зависимости от того, как часто выполняются задачи.   -  person Pshemo    schedule 08.04.2015


Ответы (9)


Вероятно, самое простое и предпочтительное решение находится в ответе Marco13, поэтому не обманывайтесь разницей голосов (этот ответ на несколько лет старше) или отметка о приемке (это просто означает, что это решение было подходящим для обстоятельств OP, а не как лучшее в целом).


Вы можете использовать ThreadFactory для установки потоков внутри Executor на демонов. Это повлияет на службу исполнителя таким образом, что он также станет потоком демона, поэтому он (и потоки, обрабатываемые им) остановятся, если не будет другого потока, не являющегося демоном. Вот простой пример:

ExecutorService exec = Executors.newFixedThreadPool(4,
        new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread t = Executors.defaultThreadFactory().newThread(r);
                t.setDaemon(true);
                return t;
            }
        });

exec.execute(YourTaskNowWillBeDaemon);

Но если вы хотите получить исполнителя, который позволит завершить свою задачу и в то же время автоматически вызовет свой метод shutdown(), когда приложение будет завершено, вы можете заключить своего исполнителя с помощью Гуавы _ 4_.

ExecutorService exec = MoreExecutors.getExitingExecutorService(
        (ThreadPoolExecutor) Executors.newFixedThreadPool(4), 
        100_000, TimeUnit.DAYS//period after which executor will be automatically closed
                             //I assume that 100_000 days is enough to simulate infinity
);
//exec.execute(YourTask);
exec.execute(() -> {
    for (int i = 0; i < 3; i++) {
        System.out.println("daemon");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});
person Pshemo    schedule 14.12.2012
comment
Несмотря на то, что этот ответ был принят, я не думаю, что это то, что предполагал OP: я думаю, что программа должна завершиться, когда все задачи будут обработаны. При установке потока исполнителей на «демон» программа завершится, даже если еще есть задачи, которые нужно выполнить. - person Arnout Engelen; 05.04.2015
comment
@ArnoutEngelen Я обновил свой ответ, чтобы дать альтернативный подход, который также решает эту проблему, но я считаю, что сообщение Marco13 содержит лучший подход. - person Pshemo; 05.04.2015

Уже есть встроенная функция для создания ExecutorService, которая завершает все потоки после определенного периода бездействия: вы можете создать ThreadPoolExecutor, передать ему желаемую информацию о времени, а затем вызвать allowCoreThreadTimeout(true) в этой службе исполнителя:

/**
 * Creates an executor service with a fixed pool size, that will time 
 * out after a certain period of inactivity.
 * 
 * @param poolSize The core- and maximum pool size
 * @param keepAliveTime The keep alive time
 * @param timeUnit The time unit
 * @return The executor service
 */
public static ExecutorService createFixedTimeoutExecutorService(
    int poolSize, long keepAliveTime, TimeUnit timeUnit)
{
    ThreadPoolExecutor e = 
        new ThreadPoolExecutor(poolSize, poolSize,
            keepAliveTime, timeUnit, new LinkedBlockingQueue<Runnable>());
    e.allowCoreThreadTimeOut(true);
    return e;
}

ИЗМЕНИТЬ Ссылаясь на примечания в комментариях: Обратите внимание, что этот исполнитель пула потоков не будет автоматически завершать работу при выходе из приложения. Исполнитель продолжит работу после выхода из приложения, но не дольше keepAliveTime. Если, в зависимости от конкретных требований приложения, keepAliveTime должен быть длиннее нескольких секунд, решение в ответе Pshemo может быть более подходящим: если потоки настроены как потоки демона, они немедленно завершатся при выходе из приложения.

person Marco13    schedule 05.04.2015
comment
Это очень интересная информация. +1. Так что, если я правильно понимаю, мы можем просто ускорить освобождение потоковых ресурсов Executor, в том числе и основных. Но просто нехватка ресурсов не означает, что исполнитель будет отключен, когда будет удален последний ресурс. Завершение работы будет вызываться автоматически, когда у Executor не будет ресурсов (и новых задач для запуска) и приложение будет завершено. - person Pshemo; 05.04.2015
comment
@Pshemo Да, это не выключение в смысле как в методе shutdown. Я немного изменил это, надеюсь, теперь это менее двусмысленно. - person Marco13; 05.04.2015
comment
@Pshemo Спасибо за вашу щедрость. Это много внимания для подсказки о вызове одного метода ;-) - person Marco13; 06.04.2015
comment
Я здесь, в Stack Overflow, чтобы найти именно такие ответы: простые, точные, но в то же время не очень очевидные (по крайней мере, для меня). Хотел бы я почаще проявлять щедрость :) - person Pshemo; 06.04.2015
comment
@ Marco13 Это интересная функция, я не знал об этом, но не думаю, что я решаю проблему должным образом. Предположим, кто-то создает ExecutorService с помощью вашего метода и отправляет ему некоторую задачу, а затем метод main() завершается. После выполнения задачи ExecutorService будет жить в течение keepAliveTime, ожидая, пока не умрут все основные потоки. Это означает, что когда вы пытаетесь изящно остановить свое Java-приложение, вам нужно подождать некоторое (потенциально большое) количество времени. Не думаю, что это хорошо. @Pshemo Я считаю, что ваш ответ на данный момент лучший. - person Vlad; 07.04.2015
comment
@VladimirS. Ты прав. Приемлемость такого поведения также может зависеть от шаблона приложения. Обычно я даю им keepAliveTime несколько секунд (и, надо признать, с трудом могу себе представить, когда время больше, чем несколько секунд, было бы подходящим или даже необходимым). Если требуется увеличить keepAliveTime и по-прежнему немедленно выйти, предпочтительнее будет решение потока демона. - person Marco13; 07.04.2015
comment
@ Marco13 Да, вы тоже правы, это вопрос требований приложения. В свою очередь, я не могу представить, как можно использовать ExecutorService с allowCoreThreadTimeOut(true) и keepAliveTime достаточно маленькими, чтобы позволить ему умереть более или менее быстро. Какая польза от таких ExecutorService? После небольшого периода бездействия все основные потоки умрут, и отправка новой задачи приведет к созданию нового потока со всеми его накладными расходами, поэтому моя задача не начнется сразу. Это больше не будет пулом. - person Vlad; 07.04.2015
comment
@VladimirS. Это обсуждение (хотя и интересное) может выходить за рамки некоторых комментариев. Но я думаю, что во многих случаях (за исключением специальных приложений, критичных к производительности) накладные расходы на создание потока незначительны, и основная цель ExecutorService - не объединять потоки, а предлагать сервис с точки зрения выполнения задач, планирования, Futures и т. д. Но могут быть приложения, в которых объединение в пул более важно. Можно сказать, что описанная выше служба-исполнитель - это cachedExecutorService с ограниченным числом потоков. - person Marco13; 07.04.2015
comment
@ Marco13 Я понимаю вашу точку зрения, согласен. Я мог бы быть немного предвзят, работая над приложением для торговли валютой со всеми его последствиями, связанными с производительностью :) - person Vlad; 07.04.2015
comment
Интересно, в чем разница между разрешением основного потока на тайм-аут и простой установкой основных потоков на 0. - person lapo; 29.08.2017
comment
@lapo Если рабочая очередь пуста, наличие corePoolSize>0 означает, что всегда будет поток, ожидающий новых задач. Для ==0 передача новой задачи всегда может вызвать создание нового потока. Что касается использования и поведения, разница может быть не так уж и важна. (Хотя запуск нового потока может быть дорогостоящим, по сравнению с простым пробуждением существующего, разница вряд ли будет заметна почти во всех случаях применения) - person Marco13; 29.08.2017
comment
@ Marco13 о, я вижу: с core>0, но с включенным тайм-аутом ядра они постоянно убиваются и воссоздаются (в режиме ожидания), но таким образом поток готов, когда приходит задание. Я, вероятно, предпочитаю избегать этого, но это зависит от варианта использования. - person lapo; 29.08.2017
comment
@ Marco13 Этого также можно добиться, изменив ThreadPoolExecutor, возвращаемый newFixedThreadPool. Взгляните на мой ответ. - person Steve; 09.09.2018

Я бы использовал класс Guava ThreadFactoryBuilder.

ExecutorService threadPool = Executors.newFixedThreadPool(THREADS, new ThreadFactoryBuilder().setDaemon(true).build());

Если вы еще не используете Guava, я бы выбрал подкласс ThreadFactory, как описано в верхней части ответа Pshemo

person Phil Hayward    schedule 08.04.2015
comment
Можете ли вы объяснить, в чем разница между подходом вашего ответа и принятым ответом? Из того, что я проверил в исходном коде ThreadFactoryBuilder, он создаст ThreadFactory с тем же поведением, что и в моем ответе. Я что-то пропустил, или пункт вашего ответа заключался в том, чтобы показать, что с помощью Guava мы можем немного сократить этот код? Кстати, я не говорю, что ваш ответ неправильный, я просто пытаюсь понять его лучше. - person Pshemo; 08.04.2015
comment
Это более лаконично. Если вы уже используете Guava, зачем переписывать код, существующий в библиотеке? Вы правы, что он делает то же самое. - person Phil Hayward; 08.04.2015

Если вы хотите использовать его только в одном месте, вы можете встроить реализацию java.util.concurrent.ThreadFactory, например для пула с 4 потоками вы должны написать (пример показан как лямбда для Java 1.8 или новее):

ExecutorService pool = Executors.newFixedThreadPool(4,
        (Runnable r) -> {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        }
);

Но обычно я хочу, чтобы все мои фабрики Thread создавали потоки демонов, поэтому я добавляю служебный класс следующим образом:

import java.util.concurrent.ThreadFactory;

public class DaemonThreadFactory implements ThreadFactory {

    public final static ThreadFactory instance = 
                    new DaemonThreadFactory();

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }
}

Это позволяет мне легко передавать DaemonThreadFactory.instance ExecutorService, например

ExecutorService pool = Executors.newFixedThreadPool(
    4, DaemonThreadFactory.instance
);

или используйте его, чтобы легко запустить поток daemon из Runnable, например

DaemonThreadFactory.instance.newThread(
    () -> { doSomething(); }
).start();
person isapir    schedule 19.08.2018

да.

Вам просто нужно создать свой собственный ThreadFactory класс, который создает потоки демонов, а не обычные потоки.

person SLaks    schedule 14.12.2012

Это решение похоже на решение @ Marco13, но вместо создания нашего собственного ThreadPoolExecutor мы можем изменить то, которое возвращает Executors#newFixedThreadPool(int nThreads). Вот как:

ExecutorService ex = Executors.newFixedThreadPool(nThreads);
 if(ex instanceof ThreadPoolExecutor){
    ThreadPoolExecutor tp = (ThreadPoolExecutor) ex;
    tp.setKeepAliveTime(time, timeUnit);
    tp.allowCoreThreadTimeOut(true);
}
person Steve    schedule 09.09.2018
comment
Придирка здесь: вы не можете быть уверены, что newFixedThreadPool вернет ThreadPoolExecutor. (Так будет и почти наверняка будет вечно, но вы этого не знаете) - person Marco13; 09.09.2018

Вы можете использовать Guava ThreadFactoryBuilder. Я не хотел добавлять зависимость, и мне нужна была функциональность от Executors.DefaultThreadFactory, поэтому я использовал композицию:

class DaemonThreadFactory implements ThreadFactory {
    final ThreadFactory delegate;

    DaemonThreadFactory() {
        this(Executors.defaultThreadFactory());
    }

    DaemonThreadFactory(ThreadFactory delegate) {
        this.delegate = delegate;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = delegate.newThread(r);
        thread.setDaemon(true);
        return thread;
    }
}
person etherous    schedule 04.07.2019

Это не точно ответ на поставленный вопрос, но может оказаться полезным:

Как отмечали другие, вы можете создать DaemonThreadFactory, либо встроенный, либо как служебный класс.

Создав подкласс Runnable, вы можете получить результирующий DaemonThread из вышеуказанного Factory для выполнения всего исполняемого модуля в отдельно созданном не-демоническом потоке. При нормальных обстоятельствах этот поток, не являющийся демоном, будет завершен, даже если поток демона, используемый в Executor, предназначен для закрытия.

Вот небольшой пример:

import static java.util.concurrent.TimeUnit.*;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.*;

public class ScheduleStackOverflow {

    private static final DateTimeFormatter DTF_HH_MM_SS = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");

    private static final class ThreadRunnable implements Runnable {

        private final Runnable runnable;

        public ThreadRunnable(final Runnable runnable) {
            this.runnable = runnable;
        }
        @Override
        public void run() {
            final Thread delegateThread = new Thread(this.runnable);
            /**/         delegateThread.setDaemon(false); // TODO Try changing this to "true"
            /**/         delegateThread.start();
        }
    }

    public static void main(final String[] args) throws InterruptedException {

        final     ThreadFactory daemonThreadFactory = (daemonRunnable) -> {
            final Thread        daemon = new Thread   (daemonRunnable);
            /**/                daemon.setDaemon(true);
            return              daemon;
        };

        final Runnable runnable = new ThreadRunnable(() -> {
            System.out.println(DTF_HH_MM_SS.format(LocalTime.now()) + " daemon=" + Thread.currentThread().isDaemon() + " Scheduling...");

            sleep(5, SECONDS);

            System.out.println(DTF_HH_MM_SS.format(LocalTime.now()) + " daemon=" + Thread.currentThread().isDaemon() + " Schedule done.");
        });

        Executors
        .newSingleThreadScheduledExecutor(daemonThreadFactory)
        .scheduleAtFixedRate(runnable, 0, Duration.ofSeconds(10).toNanos(), NANOSECONDS);

        sleep(12, SECONDS);

        System.out.println(DTF_HH_MM_SS.format(LocalTime.now()) + " Main CLOSED!");
    }

    private static void sleep(final long timeout, final TimeUnit timeunit) {
        try {timeunit.sleep(timeout);} catch (InterruptedException e) {}
    }
}
person Dave The Dane    schedule 21.03.2021

Если у вас есть известный список задач, вам вообще не нужны потоки демонов. Вы можете просто вызвать shutdown () для ExecutorService после отправки всех ваших задач.

Когда ваш основной поток завершен, используйте метод awaitTermination (), чтобы дать время для выполнения отправленных задач. В настоящее время отправленные задачи будут выполнены, а пул потоков завершит свой управляющий поток после их завершения.

for (Runnable task : tasks) {
  threadPool.submit(task);
}
threadPool.shutdown();
/*... do other stuff ...*/
//All done, ready to exit
while (!threadPool.isTerminated()) {
  //this can throw InterruptedException, you'll need to decide how to deal with that.
  threadPool.awaitTermination(1,TimeUnit.SECOND); 
}
person Phil Hayward    schedule 08.04.2015
comment
Эта стратегия не будет прерывать / останавливать задачи, которые следуют шаблону «запускать до прерывания», в котором потоки будут продолжать выполняться (поскольку они не являются демонами и не прерываются). Намного безопаснее установить потоки в режим демона, если их действительно важно остановить. - person Krease; 23.06.2016