Skip to content

Chronometer

Raven Computing edited this page Mar 4, 2020 · 4 revisions

Chronometer

As the name suggests, Chronometer is a class for measuring time. It makes code more readable by not having to make calls to System.currentTimeMillis() and then doing all the calculations. Furthermore, a Chronometer can be used to execute FutureActions.

A simple example for measuring time would be:

Chronometer chron = new Chronometer().start();
doMagic();
chron.stop();

The above code will construct a new Chronometer instance, start it, do some magic and, when doMagic() returns, stop time measurement. This effectively measures the runtime of the doMagic() method. You may then call one of the elapsed*() methods to get the total time elapsed in the desired unit. Alternatively, since Chronometer overrides the toString() method, you can print the time as a formatted string:

System.out.println(chron);

NOTE: You don't necessarily have to stop your Chronometer instance in order to measure time.

Executing FutureActions

A Chronometer is also used for executing actions at some specified time in the future. When a FutureAction is given to the execute() method the Chronometer takes care of the timed execution of the specified action. The code to be executed can be generally specified either as a Runnable or as an Actable object. A simple example would look like this:

Chronometer chron = new Chronometer();
chron.execute(FutureAction.in(5, TimeUnit.SECONDS, () -> System.out.println("Hello Future")));

The above code will print "Hello Future" after 5 seconds. In this case the specified action is implemented with a lambda expression as a plain Runnable. It's important to understand that the execute() method is non-blocking. A FutureAction submitted to a Chronometer will be handled by a background daemon thread. That thread is responsible for all timed executions of FutureActions submitted to the underlying Chronometer instance. There can only be one such thread for each Chronometer instance. As it is a daemon thread, it will not prevent the JVM from shuttting down when all other threads have terminated. Any thread can wait for the termination of any FutureAction. The execute() method returns the FutureAction supplied to it, so you might do this:

Chronometer chron = new Chronometer();
FutureAction action = chron.execute(FutureAction.in(5, TimeUnit.SECONDS, () -> { }));

//do something else here

//blocks until the action terminates
action.awaitTermination();

Once a FutureAction is given to a Chronometer, its timing cannot be changed. However, a FutureAction can always be cancelled by calling its cancel() method or stopping the underlying Chronometer. Stopping a Chronometer will cancel all pending FutureActions and shut down the daemon thread.

Recurrent FutureActions

Every FutureAction has a count. It determines whether a FutureAction is singular or recurrent. A singular FutureAction is executed once and then terminates whereas a recurrent FutureAction is executed n times where n is the count of that FutureAction. A simple example for a recurrent action is:

chron.execute(FutureAction.every(5, TimeUnit.SECONDS, () -> { }));

The above code will infinitely execute the FutureAction every 5 seconds until either the action itself is cancelled or the underlying Chronometer instance is stopped. A count can be set explicitly:

chron.execute(FutureAction.every(5, TimeUnit.SECONDS, () -> { }).setCount(3));

The above code will execute the FutureAction every 5 seconds for a total number of 3 times. It can still be cancelled prematurely with the mentioned methods, though. The count can also be adjusted after a FutureAction has already been submitted to a Chronometer.

Working with Actables

Instead of using a java.lang.Runnable with a FutureAction, you can also use an Actable. The Actable interface is very similar to the Runnable interface as it is functional, i.e. it has only one abstract run() method and can therefore be used with lambda expressions. The difference is that the run() method of the Actable interface has an Action parameter which can become helpful in some situations where additional data must be somehow given to a runnable instance. Concrete implementations can then query an additional argument dynamically and adjust the execution logic based on the result. When working with Chronometers and FutureActions, the Action parameter of an Actable is in fact the concrete FutureAction itself. That way a caller can specify an argument for the FutureAction submitted to a Chronometer:

String arg = "Phil";
chron.execute(FutureAction.every(5, TimeUnit.SECONDS, (action) -> {
    System.out.println("Hello " + action.getArgument(String.class));
}).with(arg));

If a reference to the FutureAction is kept around, then any thread may set a new argument for that FutureAction at any time by handing it to the with() method. You can use any object as an argument.

Using Method References

Since both Runnable and Actable are functional interfaces, you can use method references where suitable to make your code more readable. The following example shows how to do it:

import static java.util.concurrent.TimeUnit.SECONDS;

public class Test {

    public static void main(String[] args){
        Chronometer chron = new Chronometer();
        FutureAction action = chron.execute(FutureAction.every(5, SECONDS, Test::doStuff).setCount(3));

        action.awaitTermination();
        System.out.println("Done " + chron);
    }

    public static void doStuff(){
        System.out.println("Hello Future");
    }
}

In the above example the doStuff() method is the equivalent of using a lambda expression with a Runnable. If you want to use an Actable instead, you only have to add an Action parameter to the static method:

import static java.util.concurrent.TimeUnit.SECONDS;

public class Test {

    public static void main(String[] args) throws Exception{
        Chronometer chron = new Chronometer();
        FutureAction action = chron.execute(FutureAction.every(5, SECONDS, Test::doStuff)
                .with("Phil")
                .setCount(3));

        SECONDS.sleep(7);
        action.with("Mike");
        action.awaitTermination();
        System.out.println("Done " + chron);
    }
    
    public static void doStuff(Action action){
        System.out.println("Hello " + action.getArgument(String.class));
        System.out.println("Remaining: " + action.getCount());
    }
}

As shown above, you can then access the argument and other information directly through the action. Other threads can change the argument dynamically if they have access to the FutureAction. In the shown example, the main thread lets a Chronometer execute a FutureAction for a total of 3 times after every 5 seconds. After sleeping for 7 seconds, the main thread changes the argument for the FutureAction to "Mike". It then waits for the FutureAction to complete.

Executors

If not specified otherwise, the Runnable or Actable of a FutureAction is executed directly by the thread which manages the timing. For short-running tasks that's completely acceptable, but if one Chronometer has to handle a lot of FutureActions simultaneously or the executed tasks make blocking calls, then that might cause problems for the timing of subsequent FutureActions. In those situations it is favorable to specify an Executor which should handle the actual execution of the Runnable or Actable. It can be specified via the executedBy() method. The following example illustrates this:

import static java.util.concurrent.TimeUnit.SECONDS;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {

    static ExecutorService threadPool = Executors.newFixedThreadPool(4);

    public static void main(String[] args){
        Chronometer chron = new Chronometer();
        chron.execute(FutureAction.in(7, SECONDS, Test::doStuff)
                .with("Mike"));
        
        FutureAction action = chron.execute(FutureAction
                .every(5, SECONDS, Test::doStuff)
                .with("Phil")
                .executedBy(threadPool)
                .setCount(3));

        action.awaitTermination();
        System.out.println("Done " + chron);
        threadPool.shutdownNow();
    }
    
    public static void doStuff(Action action){
        System.out.println("Hello " + action.getArgument(String.class));
        System.out.println("Thread: " + Thread.currentThread().getName());
    }
}

In the shown example, the main thread first lets a Chronometer execute a FutureAction once after 7 seconds with the argument "Mike". As no Executor is explicitly specified, the doStuff() method will be executed by the daemon thread responsible for the timing. On the other hand, the main thread then also lets the Chronometer execute a recurrent FutureAction and explicitly specifies an Executor which should execute the doStuff() method. In this case the Executor is actually a thread pool with 4 worker threads. When the time has come, the FutureAction, which is itself a Runnable, submits itself to the specified thread pool for processing. The daemon thread of the underlying Chronometer instance therefore only has to submit the FutureAction to the Executor and not actually execute the doStuff() method. You can also change to a different Executor dynamically after the FutureAction has already been submitted to a Chronometer.

More Examples

The following code executes a FutureAction once after 5 seconds. The time is specified as a java.time.Instant object. It can be any Instant as long as it is placed in the future.

Instant time = Instant.now().plusSeconds(5); //could be any instant (in the future)
chron.execute(FutureAction.at(time, () -> System.out.println("Hello Future")));

The following code executes a FutureAction once at the time specified as a ZonedDateTime object. Timing a FutureAction this way requires a correctly functioning system clock.

ZonedDateTime time = ZonedDateTime.parse("2020-03-04T13:00:00+01:00");
chron.execute(FutureAction.at(time, () -> System.out.println("Hello Future")));

The following code executes a FutureAction once at the time specified as a LocalTime object.

LocalTime time = LocalTime.parse("13:52:00");
chron.execute(FutureAction.at(time, () -> System.out.println("Hello Future")));

The following code executes a FutureAction for an infinite amount always at the specified time. Therefore the specified code is executed at the specified local time and then after every 24 hours, i.e. every day at 13:00

LocalTime coffeeTime = LocalTime.parse("13:00:00");
chron.execute(FutureAction.alwaysAt(coffeeTime, () -> System.out.println("Hello Coffee")));
Clone this wiki locally