Java FutureTasks are a great means of offloading a computation so that you can worry about it later, but they can be tricky to use. In this post we’ll dive into a few examples to explain the utility of this java feature.
A bit on Callable
Before understanding a FutureTask, we must first understand a Callable. Per the Javadocs, a Callable is defined as:
A task that returns a result and may throw an exception. Implementors define a single method with no arguments called call.
The important thing to note here is that a Callable is a computation that returns a result. For this exercise we’re going to define a method that returns a Callable whose result will be of type Integer
public static Callable<Integer> generateCallable() { return new Callable<Integer>() { public Integer call() { Random random = new Random(); int result = 0; //wasting clock cycles here to simulate a long running computation for (int i = 0; i < 1000000000; i++) { result = random.nextInt(); } return result; } }; }
So now we have a Callable that we can use and get an Integer back. An example usage would be
public static void useCallable() { Callable<Integer> c = generateCallable(); try { Integer myValue = c.call(); System.out.println("The value from the callable is " + myValue); } catch(Exception e) { } }
Using FutureTask
The problem with using a Callable directly is that the code invoking call
has to block. This will cause the rest of the application to wait which can become a problem if several of these call
invocations are required. A FutureTask
can help alleviate the issue.
Using a FutureTask
involves 3 steps:
- Create one using the constructor that takes a
Callable
- Begin the computation by calling
run
- Call
get
on the object once you’re ready for the result
Callable<Integer> callable1 = generateCallable(); FutureTask<Integer> task1 = new FutureTask<Integer>(callable1); task1.run(); try { int result = task1.get(); System.out.println("Result1 is " + result); } catch(ExecutionException e) { System.out.println(e.getMessage() ); e.printStackTrace(); } catch(InterruptedException ie) { System.out.println(ie.getMessage() + ie.getCause()); }
A Future with Problems
The code in our previous example has some problems. We can exacerbate our problem by running 2 tasks ‘at the same time’. If you run the code below you will realize that the debug message is not printed until the calls to run
have completed.
public static void doInMainThread() { Callable<Integer> callable1 = generateCallable(); Callable<Integer> callable2 = generateCallable(); FutureTask<Integer> task1 = new FutureTask<Integer>(callable1); FutureTask<Integer> task2 = new FutureTask<Integer>(callable2); task1.run(); task2.run(); System.out.println("Called run method"); //This isn't printed until the calls to run() have completed try { int result = task1.get(); int result2 = task2.get(); System.out.println("Result1 is " + result); System.out.println("Result2 is " + result2); } catch(ExecutionException e) { System.out.println(e.getMessage() ); e.printStackTrace(); } catch(InterruptedException ie) { System.out.println(ie.getMessage() + ie.getCause()); } }
This is because FutureTask
s do not run in a separate thread by default!
Thread to the Rescue
To solve our problem we can use a Thread
. (We could also use an ExecutorService to fix this problem but we will get into that in another post) Creating a FutureTask
now involves the following 4 steps:
- Create a
FutureTask
using the constructor that takes aCallable
- Create a
Thread
that takes theFutureTask
as a param - Begin the computation by calling
start()
- Call
get
on theFutureTask
once you’re ready for the result
Let’s rewrite our example with our newfound knowledge:
Callable<Integer> callable1 = generateCallable(); Callable<Integer> callable2 = generateCallable(); FutureTask<Integer> task1 = new FutureTask<Integer>(callable1); FutureTask<Integer> task2 = new FutureTask<Integer>(callable2); Thread t1 = new Thread(task1); Thread t2 = new Thread(task2); t1.start(); t2.start(); System.out.println("Called run method"); //This is printed immediately try { int result = task1.get(); int result2 = task2.get(); System.out.println("Result1 is " + result); System.out.println("Result2 is " + result2); } catch(ExecutionException e) { System.out.println(e.getMessage()); e.printStackTrace(); } catch(InterruptedException ie) { System.out.println(ie.getMessage() + ie.getCause()); }
By running this we see that our debug message is printed immediately and does not wait on run to complete.
FutureTasks
are a useful construct that can be used anywhere that requires offloading computation. Use them to your advantage.