St.Antario
St.Antario

Reputation: 27385

Does Future require to perform the computation in a separate thread?

From the documentation

Future represents the result of an asynchronous computation.

Does it mean that a thread calling the method Future#get should not be a thread which performed the computation? So, is it appropriate if the thread calling Future#get start the computation if it has not been yet? I'm not sure If I can call it asynchronous computation...

Upvotes: 9

Views: 4546

Answers (3)

Holger
Holger

Reputation: 298153

The term “asynchronous computation” does not mandate that the computation has to run in a different thread. If the API designers intended that, they had written “computation that runs in a different thread”. Here, it simply means that there is no specification about when the computation happens.

Likewise, the existing implementations provided by the JRE do not enforce a computation in a different thread. The probably most known implementation, FutureTask can be used as follows:

Callable<String> action = new Callable<String>() {
    public String call() {
        return "hello "+Thread.currentThread();
    }
};

FutureTask<String> ft=new FutureTask<>(action);
ft.run();
System.out.println(ft.get());

Normally, instances of FutureTask are created by an ExecutorService which will determine when and by which thread the computation will be executed:

ExecutorService runInPlace=new AbstractExecutorService() {
    public void execute(Runnable command) {
        command.run();
    }
    public void shutdown() {}
    public List<Runnable> shutdownNow() { return Collections.emptyList(); }
    public boolean isShutdown() { return false; }
    public boolean isTerminated() { return false; }
    public boolean awaitTermination(long timeout, TimeUnit unit) { return false; }
};
Future<String> f=runInPlace.submit(action);
System.out.println(ft.get());

Note that this execute() implementation does not violate its contract:

Executes the given command at some time in the future. The command may execute in a new thread, in a pooled thread, or in the calling thread, at the discretion of the Executor implementation.

Note the “or in the calling thread”…

Another implementation is the ForkJoinTask:

ForkJoinTask<String> fjt=new RecursiveTask<String>() {
    protected String compute() {
        return "hello "+Thread.currentThread();
    }
};
fjt.invoke();
System.out.println(fjt.get());

Note that, while this kind of task is intended to support splitting into subtasks, which can be executed by different threads, utilizing the calling thread is intentional here. If the task can’t be split, it runs entirely in the caller’s thread as that’s the most efficient solution.


These examples are all running in the caller thread, but none of them will execute the tasks within the get() method. In principle, that would not violate the contract as it returns the result value, when the computation has been performed, however, you are likely to have trouble when trying to implement the get(long timeout, TimeUnit unit) correctly. In contrast, having a non-working cancel() would be still within the contract.

Upvotes: 7

Solomon Slow
Solomon Slow

Reputation: 27115

Technically speaking, java.util.concurrent.Future is only an interface.

It's a place holder for a value that might not be immediately available. A method that has access to a Future can:

  • Test whether the value is available,
  • get the value, or
  • "cancel" the future.

According to the API contract, the future.get() operation is allowed to block the caller until the value becomes available.

The API contract does not say where the value came from, or why it might not be available, or what mechanism might block the caller of the get() method; and while it says how "cancel" should appear to behave, it doesn't say anything about what "cancel" actually should do. That's all implementation dependent.

Future was intended to be returned by executorService.submit(callable).

If your method obtains a Future from one of the built-in ExecutorService implementations, then when you call future.get(), you will in fact be waiting for another thread to produce the result.

Upvotes: 1

vanOekel
vanOekel

Reputation: 6538

A Future.get() can be done using the same thread that did the computation, but the net result is that nothing is done concurrently. It is demonstrated in the code below when the first executor is uncommented and the second one is commented out.

An extended FutureTask can be used (there are other options like the RejectedExecutionHandler) to perform the computation using the current thread in case the computation (the Callable in the code below) cannot be done concurrently.

As to whether or not this is appropriate: in some special cases this might be needed but it is not how it is intended to be used. It looks like a premature optimization to me and I would only use it if I can see (measure) that the code that follows the intended usage does not provide the required performance AND the specialized/optimized code shows a significant performance improvement (and meets the required performance).

import java.util.concurrent.*;

public class FutureGetSameThread {

    public static void main(String[] args) {

        // Serial executor with a task-queue 
        // ExecutorService executor = Executors.newSingleThreadExecutor();
        // Serial executor without a task-queue 
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
        try {
            Callable<Integer> c = new Callable<Integer>() {
                @Override 
                public Integer call() throws Exception {
                    Thread.sleep(400L); // pretend to be busy
                    return 1;
                }
            };
            final Future<Integer> f = executor.submit(c);
            Callable<Integer> c2 = new Callable<Integer>() {
                @Override 
                public Integer call() throws Exception {
                    // wait for the result from the previous callable and add 1
                    return f.get() + 1;
                }
            };
            Future<Integer> f2 = null;
            try {
                f2 = executor.submit(c2);
                System.out.println("Second callable accepted by executor with task queue.");
            } catch (RejectedExecutionException ree) {
                System.out.println("Second callable rejected by executor without task queue.");
                f2 = new FutureTaskUsingCurrentThread<Integer>(c2);
            }
            Integer result = f2.get();
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdownNow();
        }
    }

    static class FutureTaskUsingCurrentThread<V> extends FutureTask<V> {

        public FutureTaskUsingCurrentThread(Callable<V> callable) {
            super(callable);
        }

        @Override
        public V get() throws InterruptedException, ExecutionException {
            run();
            return super.get();
        }
    }

}

Upvotes: 1

Related Questions