Imports
Use import java.util.Timer;
and import java.util.TimerTask;
at the top of the file. This will allow you to simplify this:
new java.util.Timer().schedule(new java.util.TimerTask() {
to this:
new Timer().schedule(new TimerTask() {
which requires less typing and less reading, and so is arguably easier to understand.
Naming
command
is a decent name for a Command
argument; n
is a terrible name for an int
. Is that the count of the number of command objects you want submitted? You have to consult the documentation for Timer#schedule()
to discover that it is the number of milliseconds to wait before executing the command.
milliseconds
, or delay_ms
would be better names.
Similarly, solution()
is a terrible name. Solution to what? schedule_command()
might be a better choice.
Reuse existing interfaces
The difference between interface Command
and interface Runnable
is that the former uses execute()
where as the latter uses run()
. Oh, and the latter also adds the marker @FunctionalInterface
.
By creating your own Command
interface, you would force anyone who wants to use your scheduler, who already has classes that implement the standard Runnable
interface, to add another interface to their class and forward the execute()
method to the run()
method (or vis versa).
Timers and Threads
Every Command
passed to solution()
will cause the creation of a new Timer()
object, with its own timer thread. Each Timer
object can schedule multiple tasks. Additionally, all tasks owned by a Timer
execute sequentially in the same thread, reducing concurrency issues. Consider:
for (int i=0; i<3; i++) {
solution( () -> { System.out.print("Hello from command #");
Thread.sleep(1);
System.out.print(i);
Thread.sleep(1);
System.out.println("!"); }, 1000 );
}
When executed, the output might become:
Hello from command #Hello from command #0Hello from command #!
12!
!
If your scheduler used a single, common Timer
object ...
private static Timer timer = new Timer();
public static void solution(Command command, int n) {
timer.schedule(new TimerTask() {
@Override
public void run() {
command.execute();
}
}, n);
}
Then you would be guaranteed this output instead:
Hello from command #0!
Hello from command #1!
Hello from command #2!
even though each Command
was scheduled to run at the same time.
Program Termination
From the Timer
documentation:
After the last live reference to a Timer object goes away and all outstanding tasks have completed execution, the timer's task execution thread terminates gracefully (and becomes subject to garbage collection). However, this can take arbitrarily long to occur.
If you want your program to terminate quickly, you need to call .cancel()
on all Timer
objects you create, once the last scheduled task has completed (or at least been started). This could be done by scheduling a task to cancel the task timer:
timer.schedule( () -> timer.cancel() );
Job Control
You can .cancel()
a TimerTask
before it has begun executing. However, nothing is returned from schedule()
, so the caller cannot cancel any submitted tasks.
Better Implementation
Again, from the Timer
documentation:
Java 5.0 introduced the java.util.concurrent
package and one of the concurrency utilities therein is the ScheduledThreadPoolExecutor
...
private static ScheduledExecutorService executor;
public static void main(String args[]) {
executor = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> future = solution( () -> System.out.println("Hello World"), 1000);
// Use future here, if desired.
// Cleanup
executor.shutdown();
executor = null;
}
public static ScheduledFuture<?> solution(Runnable command, int delay_ms) {
return executor.schedule( command, delay_ms, TimeUnit.MILLISECONDS );
}