I've been using a Executors.newScheduledThreadPool
to run a few tasks in the background. I've set them as daemon threads, so that if you exit the program using the X button on the top right of the main window, the program exits fine.
I tried to add another ExecutorService and just by using the second one, the application no longer exits when you click the X button. The window closes, but the program is still running the scheduled thread.
What gives?
Here is the code:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ThreadTest extends Application
{
public final static ExecutorService executorService;
public final static ScheduledExecutorService scheduledExecutorService;
static
{
executorService = Executors.newFixedThreadPool(3);
scheduledExecutorService = Executors.newScheduledThreadPool(2, r ->
{
Thread thread = Executors.defaultThreadFactory().newThread(r);
thread.setDaemon(true);
return thread;
});
scheduledExecutorService.scheduleAtFixedRate((() ->
{
System.out.println("Scheduler thread");
}), 1, 1, TimeUnit.SECONDS);
//if I comment out this, the program will close fine on exit...
executorService.execute(() -> System.out.println("Thread pool"));
}
@Override
public void start(Stage primaryStage)
{
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction((ActionEvent event) ->
{
System.out.println("Hello World!");
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
CodePudding user response:
For a daemon thread:
The Java Virtual Machine exits when the only threads running are all daemon threads.
The statement:
Executors.newFixedThreadPool(3)
Will create an ExecutorService based on the defaultThreadFactory in which:
Each new thread is created as a non-daemon thread
This means, that any threads the service uses will be non-daemon threads which, if running, will prevent the shutdown of the Java Virtual Machine.
executorService.execute(() -> System.out.println("Thread pool"));
Why is it that by commenting out the actual execution, the program still exits fine? That's even with the "threadpool" still generating threads.
Behind the scenes, newFixedThreadPool is a convenience method that creates a customized ThreadPoolExecutor. That executor performs a lazy instantiation of the threads it needs so as not to consume resources if the service is never used. If you never use the the service it does not need to create threads. But if it does create a thread, it will default to a non-daemon thread.
On-demand construction
By default, even core threads are initially created and started only when new tasks arrive, but this can be overridden dynamically using method prestartCoreThread() or prestartAllCoreThreads(). You probably want to prestart threads if you construct the pool with a non-empty queue.
The original supposition posed in your question was slightly incorrect:
ScheduledExecutorService threads set as daemon stops program exiting if you use another ExecutorService
No. ScheduledExecutorService threads set as daemon are not stopping the program from exiting.
What prevents shutdown is non-daemon threads used by the other ExecutorService that you create, unrelated to the ScheduledExecutorService.
How to address the issue
There are different ways you could solve this:
Provide a custom thread factory that creates your threads as daemon threads.
You can use a similar mechanism as you used for the custom thread factory for the ScheduledExectorService.
Call the method
Executors.newFixedThreadPool(nThreads, customThreadFactory)
.Keep using the default thread factory with non-daemon threads, and invoke
shutdown()
on the executor service.When doing so, ensure that the daemon threads will properly respond to a shutdown interruption, allowing themselves to terminate execution gracefully.
In any case, I advise (if appropriate) making use of the JavaFX Application lifecycle mechanism by starting your executors in the application start method and shutting them down in the application stop method.