Превращение ExecutorService в daemon в Java

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

ExecutorService pool = Executors.newFixedThreadPool(THREADS). 

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

pool.shutdown();

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

7 ответов


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


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

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.

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

уже есть встроенная функциональность для создания 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;
}

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


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

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

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


да.

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


Это решение аналогично @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);
}

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

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 exec = Executors.newFixedThreadPool(
    4, DaemonThreadFactory.instance
);

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

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

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