I'm trying to run a program based on a scheduleAtFixedRate
ExecutorService
.
I use this service to replace a dummy while
loop.
The global idea is to have first an ExecutorService
(the scheduler) executing a Runnable
(the runnable). then, for some reason, the runnable may or may not schedule a task on an other ExecutorService
(the tasker).
Now the question is :
I have experimented a bit and can't find a proper solution.
I have tried two things : DaemonThread
, ShutdownHook
Daemon threads are not what I'm looking for, I want the scheduler to keep running until I stop the program.
A ShutdownHook was not a good solution at first sight but after some researches it seems to work.
The thing is, the ShutdownHook is runned if I execute the code with the command mvn exec:java -Dexec.mainClass="com.goodbook.App"
and stop it with Ctrl+C
, but it's not executed if I run the java code in my IDE (VScode) and stop it with the debugger tool.
So there are two more question :
ShutdownHook
executed ?First I will give you two pieces of code : the App and the Task class.
package com.goodbook;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class ShutdownHook {
private static final Logger logger = LoggerFactory.getLogger(ShutdownHook.class);
public void attachShutDownHook(ScheduledExecutorService scheduler, ScheduledExecutorService tasker) {
logger.info("attaching shutdown hook");
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
scheduler.shutdown();
tasker.shutdown();
logger.info("shutdown hook runned");
logger.info("scheduler is down : "+scheduler.isShutdown());
logger.info("tasker is down : "+tasker.isShutdown());
}
});
}
}
public class App
{
private static final Logger logger = LoggerFactory.getLogger(App.class);
public static void main( String[] args )
{
logger.info("starting program");
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(0);
ScheduledExecutorService tasker = Executors.newScheduledThreadPool(0);
ShutdownHook shutdown = new ShutdownHook();
shutdown.attachShutDownHook(scheduler, tasker);
Task t = new Task("task1");
Runnable runnable = () -> {
logger.info("running Tasker 1");
if(!t.isPlanified()){
logger.info("unplanified task found "+t.getName());
logger.info("planning...");
tasker.schedule(t, 1000, TimeUnit.MILLISECONDS);
t.setPlanified(true);
}
};
logger.info("scheduling tasks");
scheduler.scheduleAtFixedRate(runnable, 0, 1000, TimeUnit.MILLISECONDS);
}
}
package com.goodbook;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Task implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(App.class);
String name;
AtomicBoolean planified = new AtomicBoolean(false);
public Task(String name){
this.name = name;
}
@Override
public void run(){
logger.info(name+" run");
planified.set(false);
}
public Boolean isPlanified(){
return planified.get();
}
public void setPlanified(Boolean b){
planified.set(b);
}
public String getName(){
return name;
}
}
The end of the log file in the case of running with VScode and stopping execution with the debugger tool:
INFO 2019-04-06 16:35:54,121 [pool-1-thread-1] com.goodbook.App - planning...
INFO 2019-04-06 16:35:55,121 [pool-1-thread-1] com.goodbook.App - Running Tasker 1
INFO 2019-04-06 16:35:55,121 [pool-2-thread-4] com.goodbook.TestTask - task1 run
INFO 2019-04-06 16:35:56,121 [pool-1-thread-1] com.goodbook.App - Running Tasker 1
INFO 2019-04-06 16:35:56,121 [pool-1-thread-1] com.goodbook.App - Unplanified task found task1
INFO 2019-04-06 16:35:56,121 [pool-1-thread-1] com.goodbook.App - planning...
The end of the log file in the case of running with mvn exec:java -Dexec.mainClass="com.goodbook.App"
and stopping execution with Ctrl+C
:
INFO 2019-04-06 16:59:09,686 [pool-1-thread-1] com.goodbook.App - running Tasker 1
INFO 2019-04-06 16:59:09,688 [pool-2-thread-1] com.goodbook.Task - task1 run
INFO 2019-04-06 16:59:10,686 [pool-1-thread-1] com.goodbook.App - running Tasker 1
INFO 2019-04-06 16:59:10,686 [pool-1-thread-1] com.goodbook.App - unplanified task found task1
INFO 2019-04-06 16:59:10,687 [pool-1-thread-1] com.goodbook.App - planning...
INFO 2019-04-06 16:59:11,686 [pool-1-thread-1] com.goodbook.App - running Tasker 1
INFO 2019-04-06 16:59:11,687 [pool-2-thread-2] com.goodbook.Task - task1 run
INFO 2019-04-06 16:59:12,641 [Thread-1] com.goodbook.ShutdownHook$1 - shutdown hook runned
INFO 2019-04-06 16:59:12,642 [Thread-1] com.goodbook.ShutdownHook$1 - scheduler is down : true
INFO 2019-04-06 16:59:12,642 [Thread-1] com.goodbook.ShutdownHook$1 - tasker is down : true
I really need help with these problems, I'm not a java dev :s
Shutdown hooks are called when the JVM is shutting down; see the class JavaDoc at Runtime.addShutdownHook
. But terminating a run from a debugger is not a shutdown.
When either the last non-daemon thread exits, or System.exit()
is called via code, or CTRL-C is pressed on the command line, (or the program is sent a kill signal, eg. using task manager), then the shutdown hooks are executed, and once they have completed, the JVM shuts down.
If you terminate the program during debugging, and press "stop", the IDE aborts your app immediately. There is no chance to run the shutdown hooks. This is usually the right thing to do; when debugging, you normally want to just kill whatever is running.
If you want a way to run the shutdown hooks while debugging, there are a number of ways; one is calling System.exit()
via code; that will trigger the hooks. In this related question, they wait for a keypress event before calling system.exit.
Regarding
how am I supposed to stop all these Threads when I stop the program ?
again, you can call System.exit()
so that the program will terminate even if non-deamon threads are in the middle of an execution (which is not really clean); but a better way is to give every thread a chanche to terminate normally- eg. if a thread is running an infinite loop, it could periodically check a (volatile) shutdown
variable, and return (via code) when the variable becomes set. (the shutdown
variable itself should be set by a shutdown hook)