Reputation: 27385
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
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
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:
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
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