Listen to the Future with Google Guava

Futures are one of the many concepts that are useful for building scalable software. A future is an object that acts as a placeholder for the result of some operation that has yet to be performed. This is useful for asynchronous, non-blocking programming, which is what you want to do to write scalable software.

What is a Future? An analogy

Imagine that you’re in a self-serve restaurant, where you have to stand in line, order a meal and then wait for the cook to prepare it for you, before you move on to the cash register. This is an example of a synchronous process: you (and everybody behind you) have to wait until your meal is ready before you move on to the next step.

Now, imagine another restaurant, where you order your meal, and instead of having to wait until it’s ready you get a buzzer which will buzz when your food is ready. You can move on to the cash register immediately, pay and find a table, and after some time the buzzer goes off and you go and get your food.

The buzzer is exactly what a future is: a placeholder object for your food. It makes the whole process more efficient because you can go on and do other things instead of waiting until your food is ready, and it’s also more scalable on the side of the cooks – the cooks can take orders from as many customers as they can handle, prepare multiple meals simultaneously, and to scale the process up you just have to add more cooks. The buzzers make the process asynchronous and non-blocking.

Futures in Java

Java has had support for futures since Java 5 in the form of interface java.util.concurrent.Future. However, the usefulness of Java’s interface Future is limited. The only way to get at the result that the Future contains is by calling the get() method on it, which blocks if the value isn’t yet available. Blocking negates the whole reason of why you would want to use a future – it’s like standing still in line at the restaurant, waiting for the buzzer to go off before you move on to the cash register, bringing you back to the old synchronous, blocking process.

What you really need is a way to get notified asynchronously when the result is available. Google’s Guava library has a number of useful interfaces and classes for this, which we’ll look at in this post.

Listening to the Future with Guava

Guava has an interface ListenableFuture, which is a future to which you can attach a callback which will get called when the result is available, so that you don’t have to call get() and make a thread block until the result is available. Suppose that we have a class Cook which has a method prepareMeal(), like this:

public class Cook {
    public ListenableFuture<Meal> prepareMeal(Order order) {
        // ...
    }
}

You can call this method and attach a callback to the ListenableFuture like this:

Cook cook = ...;
Order order = ...;

ListenableFuture<Meal> future = cook.prepareMeal(order);

FutureCallback<Meal> callback = new FutureCallback<Meal>() {
    @Override
    public void onSuccess(Meal meal) {
        // Eat your meal
    }

    @Override
    public void onFailure(Throwable throwable) {
        // Something went wrong, complain to the manager
    }
};

Futures.addCallback(future, callback);

// Pay at cash register and find a table

Note that the callback will be called on the cook’s thread. This could be fine if the callback task finishes quickly, but there’s also another version of Futures.addCallback() that allows you to specify an Executor to run the callback task on. Consider using that version so that the callback can run on a separate thread, freeing up the cook’s thread, especially if the processing that needs to be done in the callback is heavier. For example:

// ExecutorService for running the callback on
ExecutorService executorService = Executors.newCachedThreadPool();

// ...

Futures.addCallback(future, callback, executorService);

Cooking the Future

Now, let’s look at it from the cook’s perspective. The cook has to prepare the meal and make it available in the ListenableFuture when it’s ready. To do this, the cook can use class SettableFuture, which is a ListenableFuture on which the result can be set.

public class Cook {
    private final ExecutorService executorService = Executors.newCachedThreadPool();

    public ListenableFuture<Meal> prepareMeal(Order order) {
        SettableFuture<Meal> future = SettableFuture.create();

        // Prepare the meal in a different thread
        executorService.execute(() -> {
            // Prepare the meal
            Meal meal = ...;

           // Set the result of the future
           future.set(meal);
        });

        return future;
    }
}

This will work, but there’s an important problem with the code above. If the customer for whatever reason decides to cancel his order by calling cancel() on the future, then the task that is submitted in the cook’s ExecutorService will still run – there’s no way to remove the task from the cook’s order queue.

To fix this problem we could implement it in another way: call ExecutorService.submit(), and use Guava’s JdkFutureAdapters to wrap the regular Future returned by ExecutorService.submit() in a ListenableFuture:

public ListenableFuture<Meal> prepareMeal(Order order) {
    return JdkFutureAdapters.listenInPoolThread(executorService.submit(() -> {
        Meal meal = ...; // Prepare the meal
        return meal;
    }));
}

This will also work, and will allow you to cancel the order by calling cancel() on the returned ListenableFuture. There’s however another big problem: it’s not a scalable solution, because the wrapper returned by JdkFutureAdapters will create a blocking task that’s going to call get() on the Future returned by ExecutorService.submit() (because there’s no other way it can get the result in the plain Future) – and blocking is exactly what we want to avoid.

The right way to solve this problem is to wrap the ExecutorService in a decorator that will make it look like a ListeningExecutorService:

private final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

public ListenableFuture<Meal> prepareMeal(Order order) {
    return executorService.submit(() -> {
        Meal meal = ...; // Prepare the meal
        return meal;
    });
}

When you do it this way, then no blocking task is necessary. The decorator around the ExecutorService will submit a ListenableFutureTask to the underlying ExecutorService, which can call the callback without the need for a blocking get() call.

In Java 8, class CompletableFuture was added, which offers more or less the same features as what’s available in Guava, with a more elaborate and complicated API. We’ll have an in-depth look at CompletableFuture in a future (no pun intended…) post.

Bzzzt – enjoy your meal!

You may also like...