Why is Java Future.get(timeout) Not Reliable?

23,935

Solution 1

There is no reason to expect the test to pass. Given that you submit the task for execution and then wait on its completion, any amount of time could pass before your wait on Future#get() begins, allowing the task plenty of time to exhaust the sleep duration and complete.

In your case, we can assume that the thread running within the Executor gets focus while your main thread running through test() is on hold, despite being in a runnable state. As for the observed difference between stalling the submitted task for two and three seconds, I expect you could find situations where even three seconds is insufficient, depending on what other processes are busy doing on your computer.

Solution 2

@seh is right.

You are expecting what is commonly called "real-time" behavior from Java. This cannot be achieved reliably unless you use real-time libraries in a real-time capable Java distribution running on a real-time operating system.

Just to illustrate, the Java thread implementation in modern JVMs like HotSpot relies on the host operating system's native thread scheduler to decide what threads to run when. Unless the thread scheduler is specifically aware of real-time deadlines and stuff, it is likely to take a "whole of system" view when deciding what threads to run when. If the system is loaded, any particular thread may not get scheduled to run for seconds ... or longer ... after the conditions that prevented it running (e.g. waiting for a timer event) have passed.

Then there is the problem that the Java GC may cause all other threads to block.

If you really need real-time behavior from Java, it is available. For example:

However, you should expect to change your applications to use different APIs to give you real-time behavior.

Solution 3

I have to say, I think the other two answers currently have an unnecessarily low opinion of the Java concurrency classes. They will not give you millisecond accuracy (what "real" real-time applications expect) but they do quite well usually. I've written large scale commercial services using Futures and Executors and they normally worked within 10 milliseconds of the expected times, even under load.

I've run this test both on MacOS 10.6 with Java 1.6 and WinXP w/ Java 1.6.0_22 and both of them work as expected.

I modified the code as follows to test the accuracy:

    long time1 = System.nanoTime();

    System.out.println("Submitting");
    final Future<Object> future = exec.submit(call);
    try {
        future.get(1000, TimeUnit.MILLISECONDS);

        long time2 = System.nanoTime();
        System.out.println("No timeout after " + 
                             (time2-time1)/1000000000.0 + " seconds");

        fail("expected TimeoutException");
    } catch (TimeoutException ignore) {
        long time2 = System.nanoTime();
        System.out.println("Timed out after " +
                             (time2-time1)/1000000000.0 + " seconds");
    }
    finally {
        exec.shutdown();
    }

In XP this prints "timed out after 1.002598934 seconds" and in MacOS X it prints "timed out after 1.003158 seconds".

If the original poster would describe their OS and JDK version, perhaps we could determine if this is a particular bug.

Share:
23,935
Andrew Raphael
Author by

Andrew Raphael

Updated on July 11, 2022

Comments

  • Andrew Raphael
    Andrew Raphael almost 2 years

    Future.get(timeout) does not reliably throw the TimeoutException after the given timeout. Is this normal behavior or can I do something to make this more reliable? This test fails on my machine. However if I sleep for 3000 instead of 2000, it will pass.

    public class FutureTimeoutTest {
    @Test
    public void test() throws
        ExecutionException,
        InterruptedException {
    
        ExecutorService exec = Executors.newSingleThreadExecutor();
        final Callable call = new Callable() {
            @Override
            public Object call() throws Exception {
                 try {
                    Thread.sleep(2000);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
                return 0;
            }
        };
        final Future future = exec.submit(call);
        try {
            future.get(1000, TimeUnit.MILLISECONDS);
            fail("expected TimeoutException");
        } catch (TimeoutException ignore) {
        }
    }
    

    }