在 Java 中将 ExecutorService 转换为守护进程

我在 Java 1.6中使用了一个 ExecutoreService,仅仅通过

ExecutorService pool = Executors.newFixedThreadPool(THREADS).

当我的主线程完成后(连同线程池处理的所有任务) ,这个线程池将阻止我的程序关闭,直到我显式调用

pool.shutdown();

我可以通过将这个池使用的内部线程管理转换为后台处理程序线程来避免调用这个函数吗?还是我错过了什么。

48503 次浏览

Yes.

You simply need to create your own ThreadFactory class that creates daemon threads rather than regular threads.

Probably simplest and preferred solution is in Marco13's answer so don't get fooled by vote difference (this answer is few years older) or acceptance mark (it just means that this solution was appropriate for OP circumstances, not that it is best in general).


You can use ThreadFactory to set threads inside Executor to daemons. This will affect executor service in a way that it will also become daemon thread so it (and threads handled by it) will stop if there will be no other non-daemon thread. Here is simple example:

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

But if you want to get executor which will let its task finish, and at the same time will automatically call its shutdown() method when application is complete, you may want to wrap your executor with Guava'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();
}
}
});

There already is a built-in functionality for creating an ExecutorService that terminates all threads after a certain period of inactivity: You can create a ThreadPoolExecutor, pass it the desired timing information, and then call allowCoreThreadTimeout(true) on this executor service:

/**
* 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 Referring to the remarks in the comments: Note that this thread pool executor will not automatically shut down when the application exits. The executor will continue to run after the application exits, but no longer than the keepAliveTime. If, depending on the precise application requirements, the keepAliveTime has to be longer than a few seconds, the solution in the answer by Pshemo may be more appropriate: When the threads are set to be daemon threads, then they will end immediately when the application exits.

I would use Guava's ThreadFactoryBuilder class.

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

If you're not already using Guava, I'd go with a ThreadFactory subclass like described at the top of Pshemo's answer

If you have a known list of tasks, you don't need daemon threads at all. You can simply call shutdown() on the ExecutorService after submitting all your tasks.

When your main thread is complete, use the awaitTermination() method to allow time for the submitted tasks to complete.The currently submitted tasks will be executed, and the thread pool will terminate its control thread once they have been completed.

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

If you only want to use it in one place, then you can inline the java.util.concurrent.ThreadFactory implementation, e.g. for a pool with 4 threads you would write (example shown as a lambda assuming Java 1.8 or newer):

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

But I usually want all of my Thread factories to produce daemon threads, so I add a utility class as follows:

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

That allows me to easily pass DaemonThreadFactory.instance to the ExecutorService, e.g.

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

or use it to easily start a daemon Thread from a Runnable, e.g.

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

This solution is similar to @Marco13's but instead of creating our own ThreadPoolExecutor, we can modify the one returned by Executors#newFixedThreadPool(int nThreads). Here's how:

ExecutorService ex = Executors.newFixedThreadPool(nThreads);
if(ex instanceof ThreadPoolExecutor){
ThreadPoolExecutor tp = (ThreadPoolExecutor) ex;
tp.setKeepAliveTime(time, timeUnit);
tp.allowCoreThreadTimeOut(true);
}

You can use Guava's ThreadFactoryBuilder. I didn't want to add the dependency and I wanted the functionality from Executors.DefaultThreadFactory, so I used 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;
}
}

This doesn't exactly answer the question in hand, but it may prove useful:

As others have commented, you can create a DaemonThreadFactory, either inline or as a utility class.

By subclassing Runnable, you can get the resulting DaemonThread from the above Factory to perform the entire runnable unit within a separately spawned non-daemon Thread. Under normal circumstances this non-daemon Thread will complete even though the Daemon Thread used in the Executor is earmarked to be closed.

Here's a little example:

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) {}
}
}