将ExecutorService转换为Java中的守护程序


76

我正在Java 1.6中使用ExecutoreService,其启动方式仅由

ExecutorService pool = Executors.newFixedThreadPool(THREADS). 

当我的主线程完成时(以及线程池处理的所有任务),该池将阻止我的程序关闭,直到我显式调用

pool.shutdown();

是否可以通过某种方式将此池使用的内部线程管理转换为守护线程来避免调用此方法?还是我在这里想念什么。


1
作为当前接受的答案的作者,我建议阅读Marco13发布的方法:stackoverflow.com/a/29453160/1393766,并将接受标记从我的帖子更改为他的帖子,因为所描述的解决方案可能最简单,并且接近您最初想要实现的目标。
Pshemo

1
@Pshemo让我不同意您的意见...请根据Marco13的回答检查我的评论。
弗拉基米尔·

@Pshemo鉴于Vladimirs提示,您可以考虑指出以下事实:“简单”解决方案可能并不总是首选。
Marco13年

正是由于这个原因,@ Marco13我将“可能”放在答案的开头。但是,最好在答案中添加有关其背后思想的更多信息,也许还可以提供一些一般性建议,例如keepAliveTime根据任务执行的频率来确定合理的价值。
Pshemo

Answers:


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()在应用程序完成时自动调用其方法,则可能需要用Guava的 包装您的执行程序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的帖子中包含最佳方法。
Pshemo

52

已经有一个内置的功能来创建一个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答案中的解决方案可能更合适:如果将线程设置为守护程序线程,则它们将在应用程序退出时立即结束。


1
那是非常有趣的信息。+1。因此,如果我理解正确,我们可以简单地使执行器释放线程资源的速度更快,包括核心资源。但是,仅缺少资源并不意味着在删除最后一个资源时将关闭执行程序。当Executor没有资源(和要运行的新任务)且应用程序完成时,将自动调用Shutdown。
Pshemo

1
@Pshemo是的,这不是shutdown方法中所说的“关闭” 。我改写了一下,希望它现在不再那么模棱两可了。
Marco13年

3
@ Marco13这是一个有趣的功能,我不知道,但是我认为我不能正确解决问题。假设有人使用您的方法创建ExecutorService并向其提交一些任务,然后该main()方法完成。后任务完成,ExecutorService的能活KeepAliveTime的,等到所有核心线程死亡。这意味着,当您尝试正常停止Java应用程序时,您需要等待一些(可能很大)的时间。我认为这不好。@Pshemo我相信您的回答是目前最好的。
弗拉基米尔·

1
@VladimirS。你是对的。此行为是否可以接受还可能取决于应用程序模式。通常,我给他们keepAliveTime几秒钟的时间(顺便说一下,很难想象什么时候大于几秒钟才是合适的,甚至是必要的)。如果要求它更大keepAliveTime并且仍然立即退出,则最好使用守护程序线程解决方案。
Marco13年

1
@ Marco13是的,您也是正确的,这是应用程序要求的问题。在轮到我了,我无法想象一个人如何使用ExecutorServiceallowCoreThreadTimeOut(true)keepAliveTime足够小让它或多或少迅速死亡。这样有什么好处ExecutorService?闲置一小段时间后,所有核心线程都会死亡,并且提交新任务将导致创建新线程以及所有开销,因此我的任务将不会立即开始。它将不再是一个游泳池
弗拉基米尔·

22

我将使用番石榴的ThreadFactoryBuilder类。

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

如果您尚未使用Guava,我将使用ThreadFactory子类,如Pshemo答案顶部所述


1
您能解释一下您的答案与接受的答案之间的区别是什么吗?根据我在ThreadFactoryBuilder的源代码中检查的内容,它将创建具有与我的答案中所述行为完全相同的行为的ThreadFactory。我是否想念您的答案或要点是要表明使用番石榴可以使这段代码有所缩短?顺便说一句,我并不是说您的答案是错误的,我只是想更好地理解它。
Pshemo

4
更简洁。如果您已经在使用Guava,为什么还要重写库中存在的代码?您是正确的,它确实做同样的事情。
菲尔·海沃德

6

如果只想在一个地方使用它,则可以内联java.util.concurrent.ThreadFactory实现,例如,对于要编写的具有4个线程的池(示例显示为使用Java 1.8或更高版本的lambda):

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.instanceExecutorService,例如

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

或使用它轻松地从中启动守护进程线程Runnable,例如

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

4

是。

您只需要创建自己的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会返回ThreadPoolExecutor。(确实如此,而且几乎肯定会永远如此,但您不知道
件事

0

您可以使用Guava的ThreadFactoryBuilder。我不想添加依赖关系,我想要Executors.DefaultThreadFactory的功能,所以我使用了composition:

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

如果您有任务的已知列表,则根本不需要守护线程。提交所有任务后,您只需在ExecutorService上调用shutdown()。

当您的主线程完成时,请使用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)2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.