Перетворення ExecutorService на демон в Java


77

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

ExecutorService pool = Executors.newFixedThreadPool(THREADS). 

Коли мій основний потік буде закінчений (разом із усіма завданнями, обробленими пулом потоків), цей пул не дозволить моїй програмі зупинитися, поки я явно не покличу

pool.shutdown();

Чи можу я уникнути необхідності викликати це, як-небудь перетворивши управління внутрішнім потоком, що використовується цим пулом, у потік deamon? Або мені чогось тут не вистачає.


1
Я як автор прийнятого в даний час відповідь запропонував би читання підхід відправлений Marco13: stackoverflow.com/a/29453160/1393766 і зміна прийому знака з мого поста в його, так як рішення описано тут, ймовірно , самий простий і закривається, що ви спочатку хотіли добитися .
Пшемо,

1
@Pshemo Дозвольте не погодитися з вами ... Будь ласка, перевірте мій коментар у відповіді Marco13.
Володимир

@Pshemo З огляду на підказку Володимира, ви можете подумати про те, що "просте" рішення не завжди може бути кращим.
Marco13

@ Marco13 Я поставив "напевно" на початку моєї відповіді саме з цієї причини. Але може бути гарною ідеєю додати у свою відповідь більше інформації про ідею, що стоїть за нею, і, можливо, деякі загальні поради, наприклад, що може бути розумним значенням, keepAliveTimeзалежно від того, як часто виконуються завдання.
Пшемо

Відповіді:


102

Ймовірно, найпростіше і бажане рішення - у відповіді 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()метод, коли заявка буде завершена, можливо, ви захочете обернути свого виконавця методом Гуави 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();
        }
    }
});

1
Незважаючи на те, що ця відповідь була прийнята, я не думаю, що це передбачало OP: я думаю, що програма повинна закритися, коли всі завдання будуть оброблені. При встановленні для потоку виконавців значення `` демон '' програма буде виходити навіть тоді, коли ще потрібно виконати завдання.
Arnout Engelen,

@ArnoutEngelen Я оновив свою відповідь, давши альтернативний підхід, який також вирішує цю проблему, але я вважаю, що публікація Marco13 містить найкращий підхід.
Пшемо,

53

Вже є вбудована функціональність для створення, 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 може бути більш доречним: Коли потоки встановлені як демонові потоки, вони закінчаться негайно, коли програма закриється .


1
Це дуже цікава інформація. +1. Отже, якщо я правильно розумію, ми можемо просто зробити Executor швидшим звільненням потокових ресурсів, включаючи також основні. Але лише відсутність ресурсів не означає, що виконавець буде відключений, коли буде видалено останній ресурс. Вимкнення буде викликано автоматично, коли у Executor не буде ресурсів (і нові завдання для запуску), а додаток завершено.
Пшемо

1
@Pshemo Так, це не "відключення" у тому сенсі, як у shutdownметоді. Я трохи переформулював це, сподіваюся, зараз це менш двозначно.
Marco13,

3
@ Marco13 Це цікава функція, я її не знав, але не думаю, що правильно вирішую проблему. Припустимо, хтось створив ExecutorService, використовуючи ваш метод, і надіслав йому якесь завдання , а потім main()метод закінчить. Після того, як завдання буде виконано, ExecutorService діятиме протягом keepAliveTime , чекаючи, поки всі основні потоки загинуть. Це означає, що коли ви намагаєтеся витончено зупинити свій додаток Java, вам потрібно почекати деякий (потенційно великий) проміжок часу. Я не думаю, що це добре. @Pshemo Я вважаю, що ваша відповідь найкраща на даний момент.
Володимир

1
@VladimirS. Ти маєш рацію. Прийнятність такої поведінки може також залежати від моделі програми. Зазвичай я даю їм keepAliveTimeкілька секунд (і, правда, навряд чи уявляю, коли час, що перевищує кілька секунд, був би доречним або навіть необхідним). Якщо потрібно мати більший розмір keepAliveTimeі все одно негайно вийти, переважно буде рішення демона.
Marco13

1
@ Marco13 Так, ти теж маєш рацію, це питання вимог до програми. У свою чергу, я не уявляю, як можна використовувати ExecutorServiceс allowCoreThreadTimeOut(true)і keepAliveTimeдосить малий, щоб дозволити йому померти більш-менш швидко. Яка користь від такого ExecutorService? Після невеликого періоду бездіяльності всі основні потоки загинуть, а подання нового завдання призведе до створення нового потоку з усіма його накладними витратами, тому моє завдання не розпочнеться відразу. Це вже не буде басейн .
Володимир

22

Я б використав клас Guava ThreadFactoryBuilder.

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

Якщо ви ще не використовуєте Guava, я б вибрав підклас ThreadFactory, як описано у верхній частині відповіді Pshemo


1
Чи можете ви пояснити, в чому різниця між підходом від вашої відповіді та прийнятою відповіддю? З того, що я перевірив у вихідному коді ThreadFactoryBuilder, він створить ThreadFactory з точно такою ж поведінкою, як це було представлено у моїй відповіді. Я щось пропустив, або суть Вашої відповіді полягала в тому, щоб показати, що з Гуавою ми можемо трохи скоротити цей код? До речі, я не кажу, що ваша відповідь неправильна, я просто намагаюся зрозуміти її краще.
Пшемо

4
Це більш лаконічно. Якщо ви вже використовуєте Guava, навіщо переписувати код, який існує в бібліотеці? Ви праві, що робить точно те саме.
Філ Хейворд,

7

Якщо ви хочете використовувати його лише в одному місці, тоді ви можете вбудувати 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
);

або скористайтеся ним, щоб легко запустити демонову нитку з Runnable, наприклад,

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

5

Так.

Вам просто потрібно створити власний ThreadFactoryклас, який створює потоки демонів, а не звичайні потоки.


0

Це рішення схоже на рішення @ 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);
}

1
Важливе значення тут: Ви не можете бути впевнені, що newFixedThreadPoolповерне a ThreadPoolExecutor. (Це відбувається і майже напевно буде назавжди, але ви цього не знаєте )
Marco13

0

Ви можете використовувати ThreadFactoryBuilder від Guava. Я не хотів додавати залежність, і я хотів функціональність від 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;
    }
}

-1

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

1
Ця стратегія не перериватиме / зупинятиме завдання, які слідують за шаблоном "запуск до переривання", в якому потоки продовжуватимуть працювати (оскільки вони не демон і не перериваються). Набагато безпечніше встановлювати потоки для демона, якщо важливо насправді зупиняти їх.
Krease
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.