Question

I am using Java Callable Future in my code. Below is my main code which uses the future and callables -

public class TimeoutThread {

    public static void main(String[] args) throws Exception {

        // starting the background thread
        new ScheduledCall().startScheduleTask();

        ExecutorService executor = Executors.newFixedThreadPool(5);
        Future<String> future = executor.submit(new Task());

        try {
            System.out.println("Started..");
            System.out.println(future.get(3, TimeUnit.SECONDS));
            System.out.println("Finished!");
        } catch (TimeoutException e) {
            System.out.println("Terminated!");
        }

        executor.shutdownNow();
    }
}

Below is my Task class which implements the Callable interface and this class needs to get the data from the ClientData class method. And I have a background thread which is setting the data in ClientData class by using the setters.

class Task implements Callable<String> {

    public String call() throws Exception {

    //.. some code

    String hostname = ClientData.getPrimaryMapping("some_string").get(some_number);

    //.. some code
    }
}

Below is my background thread which is setting the value in my ClientData class by parsing the data coming from the URL and it is running every 10 minutes.

public class ScheduledCall {

    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    public void startScheduleTask() {

        final ScheduledFuture<?> taskHandle = scheduler.scheduleAtFixedRate(
                new Runnable() {
                    public void run() {
                        try {
                            callServers();
                        } catch(Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                }, 0, 10, TimeUnit.MINUTES);
    }

    private void callServers() {
        String url = "url";
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.getForObject(url, String.class);
        parseResponse(response);

    }

    // parse the response and set it.
    private void parseResponse(String response) {
        //...       
        ConcurrentHashMap<String, Map<Integer, String>> primaryTables = null;

        //...

        // store the data in ClientData class variables which can be
        // used by other threads
        ClientData.setPrimaryMapping(primaryTables);        
    }
}

And below is my ClientData class

public class ClientData {

    private static final AtomicReference<Map<String, Map<Integer, String>>> primaryMapping = new AtomicReference<>();

    public static Map<String, Map<Integer, String>> getPrimaryMapping() {
        return primaryMapping.get();
    }

    public static void setPrimaryMapping(Map<String, Map<Integer, String>> map) {
        primaryMapping.set(map);
    }
}

PROBLEM STATEMENT:-

The only problem I am facing is, whenever I am starting the program for the first time, what will happen is, it will start the background thread which will parse the data coming from the URL. And simultaneously, it will go into call method of my Task class. And the below line will throw an exception, why? bcoz my background thread is still parsing the data and it hasn't set that variable.

String hostname = ClientData.getPrimaryMapping("some_string").get(some_number);

How do I avoid this problem? Is there any better and efficient way to do this?

Was it helpful?

Solution

You just want to make the Task wait until the first update to the Map has happened before proceeding?

public class ClientData {

    private static final AtomicReference<Map<String, Map<Integer, String>>> primaryMapping = new AtomicReference<>();
    private static final CountDownLatch hasBeenInitialized = new CountDownLatch(1);

    public static Map<String, Map<Integer, String>> getPrimaryMapping() {
        try {
            hasBeenInitialized.await();
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }
        return primaryMapping.get();
    }

    public static void setPrimaryMapping(Map<String, Map<Integer, String>> map) {
        primaryMapping.set(map);
        hasBeenInitialized.countDown();
    }
}

A simpler and more efficient way that doesn't cause synchronization checks and make you deal with stupid InterruptedException being a Checked Exception might be to simply load initial values into the Map before firing up the multi-threaded engines.....

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top