Friday, December 17, 2010

Java TimeUnit: More than just a unit of time

My conception of time, my ability to distinguish between consecutiveness and simultaneousness - seemed subtly disordered so that I formed chimerical notions about living in one age and casting one's mind all over etenity for knowledge of past and future ages.

- "The Shadow out of Time", H.P. Lovecraft
Consider the case when you need to call a method that requires you specify a time duration, for example, specifying what the timeout should be for a service making a web call. Usually the time related argument is an int or long, with an inferred unit of measurement (usually milliseconds, but sometime seconds). As you may be thinking of a unit of measurement other than what the method wants, it becomes neccessary to do a conversion. For instance, to specify a timeout of 30 seconds may neccessitate that you actually specify 30000 milliseconds. This conversion is not difficult, but it is tedious (and can cause problems when maintaining code). The good news is that conversion doesn't need to be tedious (and, in some cases, may not be necessary).

For our example, let's suppose that Cthulhu has decided the world isn't worth taking over today and so we need to write some code to have him sleep in R'lyeh for 45 days and then wake up to check things out again (think of it as scheduled down time). If we want to use Thread.sleep() we'll need to convert the 45 days into milliseconds. Not a hard calculation, the end result would look something like:

// Have Cthulhu sleep for 45 days

Hmm, not so easy to visually confirm that this really represents 45 days. So let's have the JVM do the math so it's a little easier to visually check the code:

// Have Cthulhu sleep for 45 days
   Thread.sleep(45 * 24 * 60 * 60 * 1000)

Still not the best, however it is good enough. And until recently, pretty much this is what I would write. That is, until I took the time to read more on the JavaDocs for a class I've used extensively in my work with concurrent programming. The class in question is an enum, TimeUnit, which was added in Java 5 as part of the java.util.concurrent package. TimeUnits goal is simple; to quote from the JavaDoc:
A TimeUnit represents time durations at a given unit of granularity and provides utility methods to convert across units, and to perform timing and delay operations in these units. A TimeUnit does not maintain time information, but only helps organize and use time representations that may be maintained separately across various contexts.
For example, borrowing from that same java.util.concurrent package, say you want to schedule something to run in 10 seconds. Not a problem:

ScheduledExecutorService scheduledExcecutor = Executors.newScheduledThreadPool(3);
    Runnable runMe = ... 
    scheduledExcecutor.schedule(runMe, 10, TimeUnit.SECONDS);

The take away is that if you are in going to be writing a time-based method, consider using both a TimeUnit (for setting scale) and a numeric argument (for duration). Yes, it's one more argument, but the resulting code is easier to maintain. For example, it is hard to not understand the meaning of setTimeout(250, TimeUnit.MILLISECONDS). Caveat: the method should document the finest precision is supports - you don't want the caller to think they can specify microseconds when the code works with seconds!

But the point of this article was to discuss converting from one unit of time to another. This is where the "utility methods" mentioned in the JavaDoc come into play. One in particular is relevant to our putting Cthulhu to sleep:

void sleep(long timeout)
Performs a Thread.sleep using this unit.

This makes our call much simpler:

// Have Cthulhu sleep for 45 days

However, in the words of the eternal late night TV sales pitch, but wait, there's more! TimeUnit also has wrapper methods for Thread.join() and Object.wait(). In addition, there are methods that convert from one TimeUnit to another. For example TimeUnit.DAYS.toHours(23) returns a numeric measure of how many hours there are in 23 days. (Note: TimeUnit.DAYS, TimeUnit.HOURS and TimeUnit.MINUTES were not present in Java 5; they were all added in Java 6).

These conversions all rely on a single method:

long convert(long sourceDuration, TimeUnit sourceUnit)
Convert the given time duration in the given unit to this unit.
(In case your wondering, this method truncates when converting from finer to coarser units. For example, TimeUnit.MINUTES.convert(59, TimeUnit.seconds) will return 0.)

So, with these methods in mind, it's possible to call a method such as setTimeOut(long millis)) without doing any of the conversion mathematics: just write setTimeout(TimeUnit.SECONDS.toMilliseconds(2)). The resulting code may be longer than the alternative, in this case setTimeout(2*1000), but it should prove easier to both understand and maintain.

No comments:

Post a Comment