Sunday, 18 March 2012

Getting result from thread's execution with Future

To make things go faster we parallelize our computations. What if we need to use the result of the thread's execution?
Let's say we have a service that buys some product. It needs to fetch the price and quantity of the product. Fetching the price usually takes longer, so we delegate this task to a thread, while we are dealing with quantity.
To keep things simple, our PriceChecker class will be just simulating that it does something meaningful:
public class PriceChecker {
private static final Random RANDOM = new Random();
public Double checkPrice() {
int sleepTime = RANDOM.nextInt(3);
try {
Thread.sleep(sleepTime * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Math.random();
}
}

Now it would be good to somehow get the result of checkPrice() invocation. Runnable's run() is a void method, so we would have to do something like this:
public class Buyer {
private PriceChecker priceChecker = new PriceChecker();
private Double price;
public void buyProduct() {
new Thread(new Runnable() {
@Override
public void run() {
price = priceChecker.checkPrice();
}
}).start();
int quantity = getQuantity();
while (price == null) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Buying product, price = " + price + " , quantity = " + quantity);
}
private int getQuantity() {
return new Random().nextInt(100);
}
}
view raw Buyer.java hosted with ❤ by GitHub

This approach has a lot of drawbacks. We have to check in the loop if the price has already been set. What is more, price cannot be a final variable but has to be a field instead.

To deal with this kind of a problem, the Future interface should be used. Basically, it allows to get the result of thread's execution. Let's take a look at the actual usage in the context of our example:
public class BuyerWithFuture {
private PriceChecker priceChecker = new PriceChecker();
private ExecutorService executorService = Executors.newSingleThreadExecutor();
public void buyProduct() throws Exception {
Callable<Double> getPriceTask = new Callable<Double>() {
@Override
public Double call() throws Exception {
return priceChecker.checkPrice();
}
};
Future<Double> priceFuture = executorService.submit(getPriceTask);
Double price = priceFuture.get();
int quantity = getQuantity();
System.out.println("Buying product, price = " + price + " , quantity = " + quantity);
executorService.shutdown();
}
private int getQuantity() {
return new Random().nextInt(100);
}
}
First of all we're using Callable which is similar to Runnable but is capable of returning the result. Notice that it can also throw exception, while run() cannot. When we submit the callable to executor service, we're getting the Future object. Its method get() blocks until the computation is finished. If we want to specify the maximum time that we want to wait, there is an overloaded version that takes waiting time settings as parameters.

The runner for both cases is pretty straightforward:
public class FutureExampleRunner {
public static void main(String[] args) throws Exception {
Buyer buyerWithoutFuture = new Buyer();
buyerWithoutFuture.buyProduct();
BuyerWithFuture buyerWithFuture = new BuyerWithFuture();
buyerWithFuture.buyProduct();
}
}

No comments:

Post a Comment