Matt McMinn
Matt McMinn

Reputation: 16311

Java Timer/ScheduledExecutor appears to have a minimum resolution of 10 milliseconds

I'm trying to schedule a task with a fixed-delay repetition, where the delay is 1 millisecond, and it appears that with both the Timer and the ScheduledThreadPoolExecutor classes, the best I can do is 10 millseconds. I'm not expecting to get exactly 1 millisecond resolution, but I wasn't expecting an almost perfect 10 millisecond resolution. Here's a simple class that I put together to test what's going on:

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class SchedTest
{
    private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    public static void main(String[] args)
    {
        int type = Integer.parseInt(args[0]);
        switch(type)
        {
            case 0:
                ScheduledThreadPoolExecutor e = new ScheduledThreadPoolExecutor(1);
                e.scheduleWithFixedDelay(new Runnable()
                        {
                            @Override
                            public void run()
                            {
                                System.out.println("executor " + sdf.format(new Date()));
                            }
                        }, 0, 1, TimeUnit.MILLISECONDS);
                break;

            case 1:
                Timer t = new Timer();
                TimerTask task = new TimerTask()
                {
                    @Override
                    public void run()
                    {
                        System.out.println("timer " + sdf.format(new Date()));
                    }
                };
                t.schedule(task, new Date(), 1);
                break;

            case 2:
                Thread th = new Thread()
                {
                    @Override
                    public void run()
                    {
                        while(true)
                        {
                            System.out.println("thread " + sdf.format(new Date()));
                            try
                            {
                                Thread.sleep(1);
                            }
                            catch(Exception e)
                            {
                                //
                            }
                        }
                    }
                };
                th.start();
                break;
        }
    }
}

The output that I get from this class looks like this:

executor 11:52:05.202
executor 11:52:05.212
executor 11:52:05.222
executor 11:52:05.232
executor 11:52:05.242
executor 11:52:05.252
executor 11:52:05.262
executor 11:52:05.272
executor 11:52:05.282
executor 11:52:05.292
executor 11:52:05.302

timer 11:52:08.009
timer 11:52:08.019
timer 11:52:08.029
timer 11:52:08.039
timer 11:52:08.049
timer 11:52:08.059
timer 11:52:08.069
timer 11:52:08.079
timer 11:52:08.089
timer 11:52:08.099
timer 11:52:08.109

thread 11:52:13.020
thread 11:52:13.021
thread 11:52:13.022
thread 11:52:13.023
thread 11:52:13.024
thread 11:52:13.025
thread 11:52:13.026
thread 11:52:13.027
thread 11:52:13.028
thread 11:52:13.029
thread 11:52:13.030

My understanding of the fixed-delay is that the time between two executions of the task should be timeTakenByTask + delay - so I'm not sure how that's adding up to 10 milliseconds. What am I missing here?

I'm running with JDK 1.8.0_131 on a Windows 7 laptop.

Edit If I run the following:

for(int x = 0;x < 10000;x++)
{
    System.out.println(System.currentTimeMillis());
}

The output that I get is almost always at a 10ms resolution: that is, my output looks like the following numbers (repeated several times): 1

516212971144
1516212971144
1516212971144
...
1516212971154
1516212971154
1516212971154
...
1516212971164
1516212971164
1516212971164
...
1516212971174
etc.

If I run long enough, occasionally I'll get one response that is actually 1ms between them, but 99% of the time, when the number changes it does so in 10ms increments. So I think that @VGR has it right: as Timer seems to be using System.currentTimeMillis() to keep track of time - and it's likely that ScheduledThreadPoolExecutor is as well - the granularity of that call determines how often the tasks can execute, and as @VGR noted, the docs say that granularity can vary. My machine must be one that has a 10ms granularity.

Upvotes: 2

Views: 1670

Answers (2)

VGR
VGR

Reputation: 44414

From the documentation for System.currentTimeMillis():

Note that while the unit of time of the return value is a millisecond, the granularity of the value depends on the underlying operating system and may be larger. For example, many operating systems measure time in units of tens of milliseconds.

Even if ScheduledThreadPoolExecutor weren’t using System.currentTimeMillis() directly, the granularity limitation lies with the native system, so using something like, say, Object.wait probably wouldn’t improve things.

Upvotes: 2

Jonathan Rosenne
Jonathan Rosenne

Reputation: 2227

See the specification: ScheduledThreadPoolExecutor which says:

Delayed tasks execute no sooner than they are enabled, but without any real-time guarantees about when, after they are enabled, they will commence.

The actual delay depends on factors such what else is going on in your computer, the operating system, the hardware and others.

Upvotes: 1

Related Questions