03 August 2005

Waiting as fast as I can

How to avoid an occasional problem with Thread.sleep()

Every so often you find that you need a background thread that runs at intervals to perform some background task. Of course the simplest way to do this is to use Thread.sleep():

  while (true)
{
// Perform essential task here...
...

// Now wait for 10 seconds before continuing
try
{
Thread.sleep(10000);
}
catch (InterruptedException ie)
{
}
}


This will work fine - it will suspend the thread for ten seconds each time round the loop. However, if it takes two seconds to perform the "essential task", then the task will actually be performed once every twelve seconds instead of ten. What if it's important to run every ten seconds regardless of how long it takes to run the task? Well, that's easy enough to fix:

  while (true)
{
// Perform essential task here...
...

// Now wait for next 10-second interval
try
{
// This calculates how long to wait
long interval = 10000 -
(System.currentTimeMillis() % 10000);
Thread.sleep(interval);
}
catch (InterruptedException ie)
{
}
}


This calculates how many milliseconds have elapsed since the clock last showed a time which was a multiple of ten seconds ("System.currentTimeMillis() % 10000") then subtracts that from 10000, which tells us how many milliseconds until the next 10-second multiple. Now the code suspends for that many milliseconds before returning to the top of the loop and running the core task again. The task will therefore run when the system clock shows 0, 10, 20, 30, 40 and 50 seconds after each minute, regardless of how long the task takes (as long as it's less than ten seconds, of course).

There is just one slight problem with this code: it doesn't always work. I used code just like this in a server application and found that quite often it would run the core task twice in quick succession every ten seconds, instead of once. It turned out that the thread was coming out of the Thread.sleep() early by as much as fifteen or twenty milliseconds. It would then run the core task in just a couple of milliseconds, so when it recalculated the wait interval there was still ten milliseconds or so before the next clock "tick" - actually the same clock tick that it was waiting for on the previous call. The task would then be re-executed just a few milliseconds later.

I never did figure out the root cause, although it did seem that this problem never showed up on the Windows desktop box I develop on or the small Linux system I use as a test platform. It only happened when I moved the code to the 4-processor Sun server, and that makes me think that maybe this is something to do with concurrent thread synchronization on multiprocessor systems (although I think it's more likely that it's something to do with the clock granularity).

The final fix? I wrote this as a general method that can be used to synchronize on any millisecond interval:

  public static void syncSleep(long ms)
throws InterruptedException
{
long now = System.currentTimeMillis();
long interval = (ms - (now % ms));
long expectedEndTime = now + interval;
do
{
Thread.sleep(interval);
now = System.currentTimeMillis();
interval = expectedEndTime - now;
}
while (interval > 0);
}


In this, if the Thread.sleep() returns early it doesn't matter - the code detects the fact and waits again. Now we can recode the original thread code like this:

  while (true)
{
// Perform essential task here...
...

// Now wait for next 10-second interval
try
{
syncSleep(10000);
}
catch (InterruptedException ie)
{
}
}

Labels:

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]



<< Home