Site Search:

Preloader.java

<Back

Preloader uses FutureTask to perform an expensive computation whose results are needed later.

The main thread calls start() method to start the computation early, then goes to handle other business. When the main thread later needs the ProductInfo, it calls get(), which returns the loaded data if it is ready, or waits for the load to complete if not.

Future.get() can throw ExecutionException, which is a wrapper for the Throwables originated from the Callable. We call ExecutionException.getCause() to get the Throwable, then cast the Throwable to more specific Exception types before rethrow, so that upper stream code can easily handle the exceptions.

Notice Preloader have two state variables: future and thread. Both state variables have the initialization safety guaranteed by final keyword. State variable thread is not shared, it is only accessed by main thread. The other shared variable future is an instance of thread safe class FutureTask. Furthermore, the specification of FutureTask guarantees that the transfer of future's result (an instance of ProductInfo) from the thread executing the computation to the threads retrieving the result constitutes a safe publication of the result.

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Callable;

public class Preloader {
    public static void main(String[] args) {
        Preloader pre = new Preloader();
        pre.start();
        for(int i = 0; i< 10000; i++) {
            //pretend to be busy on something else
        }
        
        try {
            System.out.println(pre.get());
        } catch (DataLoadException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    ProductInfo loadProductInfo() throws DataLoadException {
        try {
            Thread.sleep(3000);  //simulate delay
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //throw new DataLoadException();
        return null;
    }

    private final FutureTask<ProductInfo> future =
        new FutureTask<ProductInfo>(new Callable<ProductInfo>() {
            public ProductInfo call() throws DataLoadException {
                return loadProductInfo();
            }
        });
    private final Thread thread = new Thread(future);

    public void start() { thread.start(); }

    public ProductInfo get()
            throws DataLoadException, InterruptedException {
        try {
            return future.get();
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof DataLoadException)
                throw (DataLoadException) cause;
            else
                throw LaunderThrowable.launderThrowable(cause);
        }
    }

    interface ProductInfo {
    }
}

class DataLoadException extends Exception { }

class LaunderThrowable {

    /**
     * Coerce an unchecked Throwable to a RuntimeException
     * <p/>
     * If the Throwable is an Error, throw it; if it is a
     * RuntimeException return it, otherwise throw IllegalStateException
     */
    public static RuntimeException launderThrowable(Throwable t) {
        if (t instanceof RuntimeException)
            return (RuntimeException) t;
        else if (t instanceof Error)
            throw (Error) t;
        else
            throw new IllegalStateException("Not unchecked", t);
    }

}