Подтвердить что ты не робот

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

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

ExecutorService pool = Executors.newFixedThreadPool(THREADS). 

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

pool.shutdown();

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

4b9b3361

Ответ 1

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


Вы можете использовать ThreadFactory для установки потоков внутри Executor для демонов. Это повлияет на службу 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() когда приложение будет завершено, вы можете захотеть обернуть своего исполнителя в MoreExecutors.getExitingExecutorService 's MoreExecutors.getExitingExecutorService.

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

Ответ 2

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

/**
 * 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;
}

EDIT Ссылаясь на замечания в комментариях: Обратите внимание, что этот исполнитель пула потоков не будет автоматически отключен, когда приложение выйдет. Исполнитель будет продолжать работать после выхода приложения, но не больше, чем keepAliveTime. Если в зависимости от точных требований к приложению keepAliveTime должно быть больше нескольких секунд, решение в ответе Pshemo может быть более уместным: когда потоки устанавливаются как потоки демона, после чего они заканчиваются сразу после выхода приложения.

Ответ 3

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

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

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

Ответ 4

Да.

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

Ответ 5

Если вы хотите использовать его только в одном месте, вы можете java.util.concurrent.ThreadFactory реализацию 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;
        }
);

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

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

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

DaemonThreadFactory.instance.newThread(
    () -> { doSomething(); }
).start();

Ответ 6

Это решение похоже на @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);
}

Ответ 7

Вы можете использовать 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;
    }
}

Ответ 8

Если у вас есть известный список задач, вам вообще не нужны потоки демона. Вы можете просто вызвать 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); 
}